前端

  • 修改src/utils/request.js,以使登录请求正确发送到后端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

// create an axios instance
const service = axios.create({
baseURL: 'http://localhost:8201/',
timeout: 5000 // request timeout
})

// request interceptor
service.interceptors.request.use(
config => {

config.headers['Authorization'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)

// response interceptor
service.interceptors.response.use(
response => {
const res = response.data

// if the custom code is not 20000, it is judged as an error.
console.log(res);
if (res.code !== 200) {

Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 返回401:未登录
if (res.code === 401) {
MessageBox.confirm('你已被登出,可以取消停留在此页面,或者重新登陆', '确定登出', {
confirmButtonText: '重新登陆',
cancelButtonClass: '取消',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload() // 重新实例化router对象,避免bug
})
})
}
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

  • 修改/src/api/user.js文件,使得请求路径完整适配后端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import request from '@/utils/request'

export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
}

export function getInfo(token) {
return request({
url: '/user/info',
method: 'get'
})
}

export function logout() {
return request({
url: '/user/logout',
method: 'post'
})
}
  • 适当修改/src/store/modules/user.js文件,使得接收到的数据能顺利存储,尤其是token数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const actions = {
// user login
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
console.log("111");
console.log(response);
const data = response.data
console.log(data.token);
commit('SET_TOKEN', data.token)
setToken(data.token)

resolve()
}).catch(error => {
reject(error)
})
})
},

// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const data = response.data
console.log("getinfo");
console.log(data);
if (!data) {
return reject('Verification failed, please Login again.')
}

// const { name, avatar } = data
// roles=data.roles
// username = data.username
// avatar=data.avatar

commit('SET_NAME', data.username)
// commit('SET_AVATAR', avatar)
resolve(data)
}).catch(error => {
reject(error)
})
})
},

// user logout
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
removeToken() // must remove token first
resetRouter()
commit('RESET_STATE')
resolve()
}).catch(error => {
reject(error)
})
})
},
  • main.js文件中,设置请求头的token
1
2
3
4
5
6
7
import { getToken } from '@/utils/auth'
axios.interceptors.request.use(config => {
// if (store.getters.token) {
config.headers['Authorization'] = getToken()
// }
return config
})

后端

JWT工具类

  • 导入依赖包
1
2
3
4
5
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.3</version>
</dependency>
  • 创建工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.xlh.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.xlh.pojo.User;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JWTUtil {
//token有效时长
private static final long EXPIRE=30*60*1000L;
//token的密钥 可自行定义
private static final String SECRET="jwt";


public static String createToken(User user) throws UnsupportedEncodingException {
//token过期时间
Date date=new Date(System.currentTimeMillis()+EXPIRE);

//jwt的header部分
Map<String ,Object>map=new HashMap<>();
map.put("alg","HS256");
map.put("typ","JWT");

//使用jwt的api生成token
String token= JWT.create()
.withHeader(map)
.withClaim("username", user.getUsername())//私有声明
.withExpiresAt(date)//过期时间
.withIssuedAt(new Date())//签发时间
.sign(Algorithm.HMAC256(SECRET));//签名
return token;
}

/**
* 校验token的有效性
* 1 token的header和payload是否没改过
* 2 没有过期
*/
public static boolean verify(String token){
try {
//解密
JWTVerifier verifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
verifier.verify(token);
return true;
}catch (Exception e){
return false;
}
}
//无需解密也可以获取token的信息
public static String getUsername(String token){
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
}
  • 创建对外界请求的拦截器,以检查token合法性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.xlh.Interceptor;
import com.xlh.util.JWTUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 判断是否是预检请求,是则直接返回
if (request.getMethod().equals("OPTIONS")){
return true;
}
//从请求头内获取token
String token = request.getHeader("Authorization");
System.out.println("token");
System.out.println(token);
//验证令牌 如果令牌不正确会出现异常 被全局异常处理


return JWTUtil.verify(token);
}

}

  • 注册拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.xlh.config;
import com.xlh.Interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/user/login");
}
}

Mapper实现

1
2
3
4
5
6
7
8
9
10
11
12
package com.xlh.mapper;
import com.xlh.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface LoginMapper {
@Select("select id,username,password from user where username=#{username}")
public User getUserByUserName(String username);
}

Service实现

  • 接口创建
1
2
3
4
5
package com.xlh.service;
import com.xlh.pojo.User;
public interface LoginService {
public User getUser(String username);
}
  • 接口实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.xlh.service.impl;
import com.xlh.mapper.LoginMapper;
import com.xlh.pojo.User;
import com.xlh.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoginServiceImpl implements LoginService {

@Autowired
private LoginMapper loginMapper;

@Override
public User getUser(String username) {
User user=loginMapper.getUserByUserName(username);
//未查到用户
if(user==null){
return null;
}
else{
return user;
}
}
}

Controller实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.xlh.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xlh.common.api.CommonResult;
import com.xlh.pojo.User;
import com.xlh.service.LoginService;
import com.xlh.util.JWTUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
@RestController
@RequestMapping("/user")
@CrossOrigin
public class LoginController {
@Autowired
private LoginService loginService;

@RequestMapping(method = RequestMethod.POST, path="/login")
@ResponseBody
public CommonResult login(@RequestBody String requestBody) throws Exception{
JSONObject jsonObject = JSON.parseObject(requestBody);
String name=jsonObject.getString("username");
String password=jsonObject.getString("password");
User user=loginService.getUser(name);
System.out.println(user);
//登录失败
if(user==null){
return CommonResult.success(200,"登录失败");
}
else{
if(user.getPassword().equals(password)){
HashMap<String, String> map=new HashMap<>();
String token= JWTUtil.createToken(user);
map.put("token",token);
return CommonResult.success(200,"登录成功",map);
}
else{
return CommonResult.success(200,"登录失败");
}
}
}

@RequestMapping(method = RequestMethod.GET,value = "/info")
@ResponseBody
public CommonResult info(HttpServletRequest request) {
String token = request.getHeader("Authorization");
HashMap<String, String> responseData = new HashMap<>();
responseData.put("roles","admin");
String username=JWTUtil.getUsername(token);
responseData.put("username",username);
responseData.put("avatar","");
return CommonResult.success(200,"获取成功",responseData);
}

@RequestMapping(method = RequestMethod.POST,value = "/logout")
@ResponseBody
public CommonResult logout() {
return CommonResult.success("ok");
}
}

问题

  • 在加入token验证后,可能会出现前端跨域的报错,设置相关跨域配置后依然无法解决,查看浏览器网络请求,发现在请求发送时,浏览器会先发送一个预检请求,跨域报错就出在这个地方。解决方法是在后端,判断是否是预检请求,是则直接返回,具体代码见上文。