博主前端是vue的nuxt,后端是springboot

首先简单说下登录操作相关步骤:

前端把获取到的账号和密码发往后端的登录接口,后端进行校验无误后生成token发给前端,然后前端把获取的token存在cookie里(或者localstorage里,这里用的是cookie)再发往后端的根据token获取登录信息的接口以取得数据,前端获取数据后把数据也存放在cookie里,然后跳转到相应页面,并在该页面的数据加载时把cookie取得的数据赋值给v-model已经双向绑定的data数据对应属性以在页面进行展示(cookie有作用域问题,如果涉及到第三方如微信登录等操作时遇到跨域问题,使用不了cookie的数据,可以在登陆页跳转时在url后面附加获取到的token然后在展示页再拿token去后台请求数据),注销的时候需要把cookie里的token和数据都注销掉。

 

token发送方式:

一般是附加在请求头header里,至于key前后端约定好就可以,博主这里直接用的就是“token”。

 

token生成及使用方式:

博主这里使用的是jwt方式,这是一种有规则的token生成方式,由专门的工具类生成,由消息头,信息载体和签名构成,在信息载体中可以存放简单不敏感的数据进行解密后直接使用,博主这里消息体内存放着id 和昵称,如果需要还可以继续添加在消息体内(如果感觉这种方式不安全,可以使用token做key,把id或者用户名什么的存放在redis中)

 

下面是具体使用方法:

jwt依赖:

        <!-- JWT-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

jwt工具类: 

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * @author
 */
public class JwtUtils {

    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    public static String getJwtToken(String id, String nickname){

        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("test-jwt")
                .setIssuer("gbx")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("id", id)
                .claim("nickname", nickname)
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取会员id
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token"); // 与前端约定的key
        System.out.println("jwtToken:" + jwtToken);
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

使用该工具类就可以在登录成功后把查询出来的id 和 nickname 用工具转成jwt形式的token发送给前端,然后前端存放在cookie里。

这里前端使用了 js-cookie,所以需要先引入:

npm install js-cookie 

我这里为了简便,参考了vue-admin 模板的cookie数据统一管理,创建utils包,里面创建auth.js文件:

import Cookies from 'js-cookie'

const TokenKey = 'gbx_token'
const LoginInfoKey = 'loginInfo'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}

export function getLoginInfo() {
  return Cookies.get(LoginInfoKey)
}

export function setLoginInfo(loginInfo) {
  return Cookies.set(LoginInfoKey,loginInfo)
}

export function removeLoginInfo() {
  return Cookies.remove(LoginInfoKey)
}

因为token需要设置在header里面,所以需要设置拦截器对request请求进行拦截,附加上token,博主这里也参考vue-admin 模板对response进行了拦截处理(可以根据返回码响应相关信息及对response的data进行直接返回简便一步操作)。

在utils包里创建request.js文件(这里没有使用vuex,element-ui也要install引入):

import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import { getToken, removeToken, removeLoginInfo } from '@/utils/auth'

const service = axios.create({
    baseURL: 'http://127.0.0.1:9001', // url = base url + request url
    // withCredentials: true, // send cookies when cross-domain requests
    timeout: 5000 // request timeout
  })

  service.interceptors.request.use(
    config => {
      // do something before request is sent
  
      if (getToken()) {  
        // let each request carry token
        // please modify it according to the actual situation
        config.headers['token'] = getToken() // 与后端约定的key
      }
      return config
    }
  )

  service.interceptors.response.use(
    /**
     * If you want to get http information such as headers or status
     * Please return  response => response
    */
  
    /**
     * Determine the request status by custom code
     * Here is just an example
     * You can also judge the status by HTTP Status Code
     */
    response => {
      const res = response.data
  
      // if the custom code is not 20000, it is judged as an error.
      if (res.code !== 20000) {
        Message({
          message: res.message || 'Error',
          type: 'error',
          duration: 5 * 1000
        })
  
        // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
        if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
          // to re-login
          MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
            confirmButtonText: 'Re-Login',
            cancelButtonText: 'Cancel',
            type: 'warning'
          }).then(() => {
            removeToken()
            removeLoginInfo()
            location.reload()
          })
        }
        return Promise.reject(new Error(res.message || 'Error'))
      } else {
        return res
      }
    },
    error => {
      console.log('err' + error) // for debug
      Message({
        message: error.message,
        type: 'error',
        duration: 5 * 1000
      })
      return Promise.reject(error)
    }
  )
  
  export default service

创建api包用以存放接口调用:

api包里login.js:

import request from '@/utils/request'

export default {
    login(loginVo) {
        return request({
            url: `/service-ucenter/member/login`,
            method: 'post',
            data: loginVo
        })
    },
    getInfoByToken() {
        return request({
            url: `/service-ucenter/member/info/token`,
            method: 'get',
        })
    },
}

page包内login.vue:

<template>
  <div class="main">
    <div class="title">
      <a class="active" href="/login">登录</a>
      <span>·</span>
      <a href="/register">注册</a>
    </div>

    <div class="sign-up-container">
      <el-form ref="userForm" :model="user">

        <el-form-item class="input-prepend restyle" prop="mobile" :rules="[{ required: true, message: '请输入手机号码', trigger: 'blur' },{validator: checkPhone, trigger: 'blur'}]">
          <div >
            <el-input type="text" placeholder="手机号" v-model="user.mobile"/>
            <i class="iconfont icon-phone" />
          </div>
        </el-form-item>

        <el-form-item class="input-prepend" prop="password" :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]">
          <div>
            <el-input type="password" placeholder="密码" v-model="user.password"/>
            <i class="iconfont icon-password"/>
          </div>
        </el-form-item>

        <div class="btn">
          <input type="button" class="sign-in-button" value="登录" @click="submitLogin()">
        </div>
      </el-form>
      <!-- 更多登录方式 -->
      <div class="more-sign">
        <h6>社交帐号登录</h6>
        <ul>
          <li><a id="weixin" class="weixin" target="_blank" href="#"><i class="iconfont icon-weixin"/></a></li>
          <li><a id="qq" class="qq" target="_blank" href="#"><i class="iconfont icon-qq"/></a></li>
        </ul>
      </div>
    </div>

  </div>
</template>

<script>
  import { setToken, setLoginInfo } from '@/utils/auth'

  import loginApi from '@/api/login'
  export default {
    layout: 'sign',

    data () {
      return {
        user:{
          mobile:'',
          password:''
        },
        loginInfo:{},
        token: ''
      }
    },

    methods: {
      submitLogin(){
          this.$refs['userForm'].validate(valid => {
              if(valid){
                  loginApi.login(this.user)
                  .then(res => {
                      this.token = res.data.token
                      setToken(this.token)
                      loginApi.getInfoByToken()
                      .then(res => {
                          this.loginInfo = res.data.info
                          setLoginInfo(this.loginInfo)
                          window.location.href = "/"
                      })
                  })
              }
          })
      },

      checkPhone (rule, value, callback) {
        if (!(/^1[34578]\d{9}$/.test(value))) {
          return callback(new Error('手机号码格式不正确'))
        }
        return callback()
      }
    }
  }
</script>

主页面数据的处理方式:

<template>
  <div class="in-wrap">
    <!-- 公共头引入 -->
    <header id="header">
      <section>
        <div>
          <ul>

            <li v-if="!loginInfo.id" id="no-login">
              <a href="/login" title="登录">
                <em >&nbsp;</em>
                <span >登录</span>
              </a>
              |
              <a href="/register" title="注册">
                <span >注册</span>
              </a>
            </li>

            <li v-if="loginInfo.id" id="is-login-two" >
              <a href="/ucenter" title>
                <img :src="loginInfo.avatar" width="30" height="30"  alt />
                <span id="userName" >{{ loginInfo.nickname }}</span>
              </a>
              <a href="javascript:void(0);" title="退出" @click="logout()" >退出</a>
            </li>

          </ul>
         </div>
      </section>
    </header>
    <!-- /公共头引入 -->

    <nuxt />

    <!-- 公共底引入 -->
    <footer id="footer">
      <section>
      </section>
    </footer>
    <!-- /公共底引入 -->
  </div>
</template>
<script>

import { getLoginInfo, removeLoginInfo, removeToken } from '@/utils/auth'

export default {
  data() {
    return {
      token: '',
      loginInfo: {
        id: '',
        avatar: '',
        nickname: ''
      }
    };
  },
  created() {
    this.showInfo()
  },
  methods: {
    showInfo(){
      var info = getLoginInfo()
      if(info){
        this.loginInfo = JSON.parse(info)
      }
    },
    logout(){
      removeLoginInfo()
      removeToken()
      window.location.href = "/"
    }
  }
};
</script>

需要注意的是,cookie中存放的数据是字符串,取出后如果进行赋值使用需要先用JSON.parse()转换后使用。

后端获取数据的接口:

    @ApiOperation("根据token获取用户信息")
    @GetMapping("/info/token")
    public R getInfo(HttpServletRequest request){
        LoginInfoVo loginInfoVo = new LoginInfoVo();
        try {
            String id = JwtUtils.getMemberIdByJwtToken(request);
            loginInfoVo = memberService.getInfoById(id);
        } catch (Exception e) {
            throw new MyException(20001,"error");
        }
        return R.ok().data("info",loginInfoVo);
    }

对应controller上的注解:

@RestController
@RequestMapping("/service-ucenter/member")
@CrossOrigin

 

另外注意:

文中删除了自定义样式,如果引用请自己调样式,否则可能会有问题,本文只提供相应方法参考!

文中删除了自定义样式,如果引用请自己调样式,否则可能会有问题,本文只提供相应方法参考!

文中删除了自定义样式,如果引用请自己调样式,否则可能会有问题,本文只提供相应方法参考!

 

Logo

前往低代码交流专区

更多推荐