支持登录
This commit is contained in:
21
src/main/java/com/xuxd/kafka/console/beans/Credentials.java
Normal file
21
src/main/java/com/xuxd/kafka/console/beans/Credentials.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package com.xuxd.kafka.console.beans;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: xuxd
|
||||||
|
* @date: 2023/5/14 19:37
|
||||||
|
**/
|
||||||
|
@Data
|
||||||
|
public class Credentials {
|
||||||
|
|
||||||
|
public static final Credentials INVALID = new Credentials();
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
private long expiration;
|
||||||
|
|
||||||
|
public boolean isInvalid() {
|
||||||
|
return this == INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main/java/com/xuxd/kafka/console/beans/LoginResult.java
Normal file
13
src/main/java/com/xuxd/kafka/console/beans/LoginResult.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package com.xuxd.kafka.console.beans;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: xuxd
|
||||||
|
* @date: 2023/5/14 20:44
|
||||||
|
**/
|
||||||
|
@Data
|
||||||
|
public class LoginResult {
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.xuxd.kafka.console.beans.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: xuxd
|
||||||
|
* @date: 2023/5/14 18:59
|
||||||
|
**/
|
||||||
|
@Data
|
||||||
|
public class LoginUserDTO {
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
21
src/main/java/com/xuxd/kafka/console/config/AuthConfig.java
Normal file
21
src/main/java/com/xuxd/kafka/console/config/AuthConfig.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package com.xuxd.kafka.console.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: xuxd
|
||||||
|
* @date: 2023/5/9 21:08
|
||||||
|
**/
|
||||||
|
@Data
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "auth")
|
||||||
|
public class AuthConfig {
|
||||||
|
|
||||||
|
private boolean enable;
|
||||||
|
|
||||||
|
private String secret = "kafka-console-ui-default-secret";
|
||||||
|
|
||||||
|
private long expireHours;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.xuxd.kafka.console.controller;
|
||||||
|
|
||||||
|
import com.xuxd.kafka.console.beans.ResponseData;
|
||||||
|
import com.xuxd.kafka.console.beans.dto.LoginUserDTO;
|
||||||
|
import com.xuxd.kafka.console.config.AuthConfig;
|
||||||
|
import com.xuxd.kafka.console.service.AuthService;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: xuxd
|
||||||
|
* @date: 2023/5/11 18:54
|
||||||
|
**/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/auth")
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
|
||||||
|
private final AuthConfig authConfig;
|
||||||
|
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
|
public AuthController(AuthConfig authConfig, AuthService authService) {
|
||||||
|
this.authConfig = authConfig;
|
||||||
|
this.authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/enable")
|
||||||
|
public boolean enable() {
|
||||||
|
return authConfig.isEnable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResponseData login(@RequestBody LoginUserDTO userDTO) {
|
||||||
|
return authService.login(userDTO);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@ package com.xuxd.kafka.console.dao;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.xuxd.kafka.console.beans.dos.SysUserDO;
|
import com.xuxd.kafka.console.beans.dos.SysUserDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author: xuxd
|
* @author: xuxd
|
||||||
* @date: 2023/4/11 21:22
|
* @date: 2023/4/11 21:22
|
||||||
**/
|
**/
|
||||||
|
@Mapper
|
||||||
public interface SysUserMapper extends BaseMapper<SysUserDO> {
|
public interface SysUserMapper extends BaseMapper<SysUserDO> {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.xuxd.kafka.console.interceptor;
|
||||||
|
|
||||||
|
import com.xuxd.kafka.console.beans.Credentials;
|
||||||
|
import com.xuxd.kafka.console.config.AuthConfig;
|
||||||
|
import com.xuxd.kafka.console.utils.AuthUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
import javax.servlet.*;
|
||||||
|
import javax.servlet.annotation.WebFilter;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: xuxd
|
||||||
|
* @date: 2023/5/9 21:20
|
||||||
|
**/
|
||||||
|
@Order(1)
|
||||||
|
@WebFilter(filterName = "auth-filter", urlPatterns = {"/*"})
|
||||||
|
@Slf4j
|
||||||
|
public class AuthFilter implements Filter {
|
||||||
|
|
||||||
|
private final AuthConfig authConfig;
|
||||||
|
|
||||||
|
private final String TOKEN_HEADER = "X-Auth-Token";
|
||||||
|
|
||||||
|
private final String AUTH_URI_PREFIX = "/auth";
|
||||||
|
|
||||||
|
public AuthFilter(AuthConfig authConfig) {
|
||||||
|
this.authConfig = authConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||||
|
if (!authConfig.isEnable()) {
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||||
|
HttpServletResponse response = (HttpServletResponse) servletResponse;
|
||||||
|
String accessToken = request.getHeader(TOKEN_HEADER);
|
||||||
|
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
if (requestURI.startsWith(AUTH_URI_PREFIX)) {
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(accessToken)) {
|
||||||
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Credentials credentials = AuthUtil.parseToken(authConfig.getSecret(), accessToken);
|
||||||
|
if (credentials.isInvalid()) {
|
||||||
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import com.xuxd.kafka.console.utils.ConvertUtil;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
import javax.servlet.*;
|
import javax.servlet.*;
|
||||||
@@ -24,6 +25,7 @@ import java.util.Set;
|
|||||||
* @author xuxd
|
* @author xuxd
|
||||||
* @date 2022-01-05 19:56:25
|
* @date 2022-01-05 19:56:25
|
||||||
**/
|
**/
|
||||||
|
@Order(100)
|
||||||
@WebFilter(filterName = "context-set-filter", urlPatterns = {"/acl/*", "/user/*", "/cluster/*", "/config/*", "/consumer/*", "/message/*", "/topic/*", "/op/*", "/client/*"})
|
@WebFilter(filterName = "context-set-filter", urlPatterns = {"/acl/*", "/user/*", "/cluster/*", "/config/*", "/consumer/*", "/message/*", "/topic/*", "/op/*", "/client/*"})
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ContextSetFilter implements Filter {
|
public class ContextSetFilter implements Filter {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.xuxd.kafka.console.service;
|
||||||
|
|
||||||
|
import com.xuxd.kafka.console.beans.ResponseData;
|
||||||
|
import com.xuxd.kafka.console.beans.dto.LoginUserDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: xuxd
|
||||||
|
* @date: 2023/5/14 19:00
|
||||||
|
**/
|
||||||
|
public interface AuthService {
|
||||||
|
|
||||||
|
ResponseData login(LoginUserDTO userDTO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.xuxd.kafka.console.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.xuxd.kafka.console.beans.Credentials;
|
||||||
|
import com.xuxd.kafka.console.beans.LoginResult;
|
||||||
|
import com.xuxd.kafka.console.beans.ResponseData;
|
||||||
|
import com.xuxd.kafka.console.beans.dos.SysUserDO;
|
||||||
|
import com.xuxd.kafka.console.beans.dto.LoginUserDTO;
|
||||||
|
import com.xuxd.kafka.console.config.AuthConfig;
|
||||||
|
import com.xuxd.kafka.console.dao.SysUserMapper;
|
||||||
|
import com.xuxd.kafka.console.service.AuthService;
|
||||||
|
import com.xuxd.kafka.console.utils.AuthUtil;
|
||||||
|
import com.xuxd.kafka.console.utils.UUIDStrUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: xuxd
|
||||||
|
* @date: 2023/5/14 19:01
|
||||||
|
**/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class AuthServiceImpl implements AuthService {
|
||||||
|
|
||||||
|
private final SysUserMapper userMapper;
|
||||||
|
|
||||||
|
private final AuthConfig authConfig;
|
||||||
|
|
||||||
|
public AuthServiceImpl(SysUserMapper userMapper, AuthConfig authConfig) {
|
||||||
|
this.userMapper = userMapper;
|
||||||
|
this.authConfig = authConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseData login(LoginUserDTO userDTO) {
|
||||||
|
QueryWrapper<SysUserDO> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("username", userDTO.getUsername());
|
||||||
|
SysUserDO userDO = userMapper.selectOne(queryWrapper);
|
||||||
|
if (userDO == null) {
|
||||||
|
return ResponseData.create().failed("用户名/密码不正确");
|
||||||
|
}
|
||||||
|
String encrypt = UUIDStrUtil.generate(userDTO.getPassword(), userDO.getSalt());
|
||||||
|
if (!userDO.getPassword().equals(encrypt)) {
|
||||||
|
return ResponseData.create().failed("用户名/密码不正确");
|
||||||
|
}
|
||||||
|
Credentials credentials = new Credentials();
|
||||||
|
credentials.setUsername(userDO.getUsername());
|
||||||
|
credentials.setExpiration(System.currentTimeMillis() + authConfig.getExpireHours() * 3600 * 1000);
|
||||||
|
String token = AuthUtil.generateToken(authConfig.getSecret(), credentials);
|
||||||
|
LoginResult loginResult = new LoginResult();
|
||||||
|
loginResult.setToken(token);
|
||||||
|
return ResponseData.create().data(loginResult).success();
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/main/java/com/xuxd/kafka/console/utils/AuthUtil.java
Normal file
54
src/main/java/com/xuxd/kafka/console/utils/AuthUtil.java
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package com.xuxd.kafka.console.utils;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.xuxd.kafka.console.beans.Credentials;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.util.Base64Utils;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: xuxd
|
||||||
|
* @date: 2023/5/14 19:34
|
||||||
|
**/
|
||||||
|
@Slf4j
|
||||||
|
public class AuthUtil {
|
||||||
|
|
||||||
|
private static Gson gson = GsonUtil.INSTANCE.get();
|
||||||
|
|
||||||
|
public static String generateToken(String secret, Credentials info) {
|
||||||
|
String json = gson.toJson(info);
|
||||||
|
String str = json + secret;
|
||||||
|
String signature = MD5Util.md5(str);
|
||||||
|
return Base64Utils.encodeToString(json.getBytes(StandardCharsets.UTF_8)) + "." +
|
||||||
|
Base64Utils.encodeToString(signature.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isToken(String token) {
|
||||||
|
return token.split("\\.").length == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Credentials parseToken(String secret, String token) {
|
||||||
|
if (!isToken(token)) {
|
||||||
|
return Credentials.INVALID;
|
||||||
|
}
|
||||||
|
String[] arr = token.split("\\.");
|
||||||
|
String infoStr = new String(Base64Utils.decodeFromString(arr[0]), StandardCharsets.UTF_8);
|
||||||
|
String signature = new String(Base64Utils.decodeFromString(arr[1]), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
String encrypt = MD5Util.md5(infoStr + secret);
|
||||||
|
if (!encrypt.equals(signature)) {
|
||||||
|
return Credentials.INVALID;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Credentials credentials = gson.fromJson(infoStr, Credentials.class);
|
||||||
|
if (credentials.getExpiration() < System.currentTimeMillis()) {
|
||||||
|
return Credentials.INVALID;
|
||||||
|
}
|
||||||
|
return credentials;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("解析token失败: {}", token, e);
|
||||||
|
return Credentials.INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/main/java/com/xuxd/kafka/console/utils/MD5Util.java
Normal file
32
src/main/java/com/xuxd/kafka/console/utils/MD5Util.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package com.xuxd.kafka.console.utils;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author: xuxd
|
||||||
|
* @date: 2023/5/14 20:25
|
||||||
|
**/
|
||||||
|
@Slf4j
|
||||||
|
public class MD5Util {
|
||||||
|
|
||||||
|
public static MessageDigest getInstance() {
|
||||||
|
try {
|
||||||
|
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||||
|
return md5;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String md5(String str) {
|
||||||
|
MessageDigest digest = getInstance();
|
||||||
|
if (digest == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new String(digest.digest(str.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,3 +47,9 @@ logging:
|
|||||||
cron:
|
cron:
|
||||||
# clear-dirty-user: 0 * * * * ?
|
# clear-dirty-user: 0 * * * * ?
|
||||||
clear-dirty-user: 0 0 1 * * ?
|
clear-dirty-user: 0 0 1 * * ?
|
||||||
|
|
||||||
|
# 权限认证设置,设置为true,需要先登录才能访问
|
||||||
|
auth:
|
||||||
|
enable: true
|
||||||
|
# 登录用户token的过期时间,单位:小时
|
||||||
|
expire-hours: 24
|
||||||
@@ -25,12 +25,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { KafkaClusterApi } from "@/utils/api";
|
import { KafkaClusterApi, AuthApi } from "@/utils/api";
|
||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
import { mapMutations, mapState } from "vuex";
|
import { mapMutations, mapState } from "vuex";
|
||||||
import { getClusterInfo } from "@/utils/local-cache";
|
import { getClusterInfo } from "@/utils/local-cache";
|
||||||
import notification from "ant-design-vue/lib/notification";
|
import notification from "ant-design-vue/lib/notification";
|
||||||
import { CLUSTER } from "@/store/mutation-types";
|
import {AUTH, CLUSTER} from "@/store/mutation-types";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -39,6 +39,33 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
this.intAuthState();
|
||||||
|
this.initClusterInfo();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
clusterName: (state) => state.clusterInfo.clusterName,
|
||||||
|
enableSasl: (state) => state.clusterInfo.enableSasl,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapMutations({
|
||||||
|
switchCluster: CLUSTER.SWITCH,
|
||||||
|
enableAuth: AUTH.ENABLE,
|
||||||
|
}),
|
||||||
|
intAuthState() {
|
||||||
|
request({
|
||||||
|
url: AuthApi.enable.url,
|
||||||
|
method: AuthApi.enable.method,
|
||||||
|
}).then((res) => {
|
||||||
|
const enable = res;
|
||||||
|
this.enableAuth(enable);
|
||||||
|
// if (!enable){
|
||||||
|
// this.initClusterInfo();
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initClusterInfo() {
|
||||||
const clusterInfo = getClusterInfo();
|
const clusterInfo = getClusterInfo();
|
||||||
if (!clusterInfo) {
|
if (!clusterInfo) {
|
||||||
request({
|
request({
|
||||||
@@ -58,16 +85,6 @@ export default {
|
|||||||
this.switchCluster(clusterInfo);
|
this.switchCluster(clusterInfo);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
clusterName: (state) => state.clusterInfo.clusterName,
|
|
||||||
enableSasl: (state) => state.clusterInfo.enableSasl,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapMutations({
|
|
||||||
switchCluster: CLUSTER.SWITCH,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import VueRouter from "vue-router";
|
import VueRouter from "vue-router";
|
||||||
import Home from "../views/Home.vue";
|
import Home from "../views/Home.vue";
|
||||||
|
import Store from "@/store";
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
@@ -53,7 +54,9 @@ const routes = [
|
|||||||
path: "/client-quota-page",
|
path: "/client-quota-page",
|
||||||
name: "ClientQuota",
|
name: "ClientQuota",
|
||||||
component: () =>
|
component: () =>
|
||||||
import(/* webpackChunkName: "cluster" */ "../views/quota/ClientQuota.vue"),
|
import(
|
||||||
|
/* webpackChunkName: "cluster" */ "../views/quota/ClientQuota.vue"
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/user-page",
|
path: "/user-page",
|
||||||
@@ -61,6 +64,12 @@ const routes = [
|
|||||||
component: () =>
|
component: () =>
|
||||||
import(/* webpackChunkName: "cluster" */ "../views/user/UserManage.vue"),
|
import(/* webpackChunkName: "cluster" */ "../views/user/UserManage.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/login-page",
|
||||||
|
name: "Login",
|
||||||
|
component: () =>
|
||||||
|
import(/* webpackChunkName: "cluster" */ "../views/login/Login.vue"),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
@@ -70,4 +79,49 @@ const router = new VueRouter({
|
|||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
const enableAuth = Store.state.auth.enable;
|
||||||
|
if (!enableAuth) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
if (to.path === "/login-page") {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
let token = localStorage.getItem("access_token");
|
||||||
|
if (token === null || token === "") {
|
||||||
|
next("/login-page");
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let originPush = VueRouter.prototype.push;
|
||||||
|
let originReplace = VueRouter.prototype.replace;
|
||||||
|
VueRouter.prototype.push = function (location, resolve, reject) {
|
||||||
|
if (resolve && reject) {
|
||||||
|
originPush.call(this, location, resolve, reject);
|
||||||
|
} else {
|
||||||
|
originPush.call(
|
||||||
|
this,
|
||||||
|
location,
|
||||||
|
() => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
VueRouter.prototype.replace = function (location, resolve, reject) {
|
||||||
|
if (resolve && reject) {
|
||||||
|
originReplace.call(this, location, resolve, reject);
|
||||||
|
} else {
|
||||||
|
originReplace.call(
|
||||||
|
this,
|
||||||
|
location,
|
||||||
|
() => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import Vuex from "vuex";
|
import Vuex from "vuex";
|
||||||
import { CLUSTER } from "@/store/mutation-types";
|
import { CLUSTER, AUTH } from "@/store/mutation-types";
|
||||||
import { setClusterInfo } from "@/utils/local-cache";
|
import { setClusterInfo } from "@/utils/local-cache";
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
@@ -12,6 +12,9 @@ export default new Vuex.Store({
|
|||||||
clusterName: undefined,
|
clusterName: undefined,
|
||||||
enableSasl: false,
|
enableSasl: false,
|
||||||
},
|
},
|
||||||
|
auth: {
|
||||||
|
enable: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
[CLUSTER.SWITCH](state, clusterInfo) {
|
[CLUSTER.SWITCH](state, clusterInfo) {
|
||||||
@@ -28,6 +31,12 @@ export default new Vuex.Store({
|
|||||||
state.clusterInfo.enableSasl = enableSasl;
|
state.clusterInfo.enableSasl = enableSasl;
|
||||||
setClusterInfo(clusterInfo);
|
setClusterInfo(clusterInfo);
|
||||||
},
|
},
|
||||||
|
[AUTH.ENABLE](state, enable) {
|
||||||
|
state.auth.enable = enable;
|
||||||
|
},
|
||||||
|
[AUTH.SET](state, info) {
|
||||||
|
localStorage.setItem("access_token", info);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {},
|
actions: {},
|
||||||
modules: {},
|
modules: {},
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
export const CLUSTER = {
|
export const CLUSTER = {
|
||||||
SWITCH: "switchCluster",
|
SWITCH: "switchCluster",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const AUTH = {
|
||||||
|
ENABLE: "enable",
|
||||||
|
SET: "setToken",
|
||||||
|
};
|
||||||
|
|||||||
@@ -347,3 +347,14 @@ export const UserManageApi = {
|
|||||||
method: "post",
|
method: "post",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const AuthApi = {
|
||||||
|
enable: {
|
||||||
|
url: "/auth/enable",
|
||||||
|
method: "get",
|
||||||
|
},
|
||||||
|
login: {
|
||||||
|
url: "/auth/login",
|
||||||
|
method: "post",
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -2,7 +2,7 @@ import axios from "axios";
|
|||||||
import notification from "ant-design-vue/es/notification";
|
import notification from "ant-design-vue/es/notification";
|
||||||
import { VueAxios } from "./axios";
|
import { VueAxios } from "./axios";
|
||||||
import { getClusterInfo } from "@/utils/local-cache";
|
import { getClusterInfo } from "@/utils/local-cache";
|
||||||
|
import Router from "@/router";
|
||||||
// 创建 axios 实例
|
// 创建 axios 实例
|
||||||
const request = axios.create({
|
const request = axios.create({
|
||||||
// API 请求的默认前缀
|
// API 请求的默认前缀
|
||||||
@@ -10,15 +10,25 @@ const request = axios.create({
|
|||||||
timeout: 120000, // 请求超时时间
|
timeout: 120000, // 请求超时时间
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// axios.defaults.headers.common['X-Auth-Token'] = localStorage.getItem('access_token');
|
||||||
|
|
||||||
// 异常拦截处理器
|
// 异常拦截处理器
|
||||||
const errorHandler = (error) => {
|
const errorHandler = (error) => {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
|
if (error.response.status == 401) {
|
||||||
|
notification.error({
|
||||||
|
message: error.response.status,
|
||||||
|
description: "请登录",
|
||||||
|
});
|
||||||
|
Router.push({ path: "/login-page" });
|
||||||
|
} else {
|
||||||
const data = error.response.data;
|
const data = error.response.data;
|
||||||
notification.error({
|
notification.error({
|
||||||
message: error.response.status,
|
message: error.response.status,
|
||||||
description: JSON.stringify(data),
|
description: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,6 +39,10 @@ request.interceptors.request.use((config) => {
|
|||||||
config.headers["X-Cluster-Info-Id"] = clusterInfo.id;
|
config.headers["X-Cluster-Info-Id"] = clusterInfo.id;
|
||||||
// config.headers["X-Cluster-Info-Name"] = encodeURIComponent(clusterInfo.clusterName);
|
// config.headers["X-Cluster-Info-Name"] = encodeURIComponent(clusterInfo.clusterName);
|
||||||
}
|
}
|
||||||
|
const token = localStorage.getItem('access_token')
|
||||||
|
if (token) {
|
||||||
|
config.headers["X-Auth-Token"] = token;
|
||||||
|
}
|
||||||
return config;
|
return config;
|
||||||
}, errorHandler);
|
}, errorHandler);
|
||||||
|
|
||||||
|
|||||||
103
ui/src/views/login/Login.vue
Normal file
103
ui/src/views/login/Login.vue
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<a-form
|
||||||
|
:form="form"
|
||||||
|
:label-col="{ span: 5 }"
|
||||||
|
:wrapper-col="{ span: 12 }"
|
||||||
|
@submit="handleSubmit"
|
||||||
|
class="login-box"
|
||||||
|
>
|
||||||
|
<h3 class="login-title">登录kafka-console-ui</h3>
|
||||||
|
<a-form-item label="账号">
|
||||||
|
<a-input
|
||||||
|
style="width: 200px"
|
||||||
|
allowClear
|
||||||
|
v-decorator="[
|
||||||
|
'username',
|
||||||
|
{ rules: [{ required: true, message: '请输入账号' }] },
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="密码">
|
||||||
|
<a-input-password
|
||||||
|
style="width: 200px"
|
||||||
|
v-decorator="[
|
||||||
|
'password',
|
||||||
|
{ rules: [{ required: true, message: '请输入密码' }] },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :wrapper-col="{ span: 16, offset: 5 }">
|
||||||
|
<a-button type="primary" @click="handleSubmit" :loading="loading"
|
||||||
|
>登录</a-button
|
||||||
|
>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import request from "@/utils/request";
|
||||||
|
import {AuthApi} from "@/utils/api";
|
||||||
|
import notification from "ant-design-vue/lib/notification";
|
||||||
|
import {mapMutations} from "vuex";
|
||||||
|
import {AUTH} from "@/store/mutation-types";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Login",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: this.$form.createForm(this, { name: "login-form" }),
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.form.validateFields((err, values) => {
|
||||||
|
if (!err) {
|
||||||
|
this.loading = true;
|
||||||
|
const params = Object.assign({}, values);
|
||||||
|
request({
|
||||||
|
url: AuthApi.login.url,
|
||||||
|
method: AuthApi.login.method,
|
||||||
|
data: params,
|
||||||
|
}).then((res) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (res.code == 0) {
|
||||||
|
this.setToken(res.data.token);
|
||||||
|
this.$router.push("/");
|
||||||
|
} else {
|
||||||
|
notification.error({
|
||||||
|
message: "error",
|
||||||
|
description: res.msg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
...mapMutations({
|
||||||
|
setToken: AUTH.SET,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-box {
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
width: 350px;
|
||||||
|
height: 300px;
|
||||||
|
margin: 120px auto;
|
||||||
|
padding: 35px 35px 15px 35px;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
box-shadow: 0 0 25px #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 auto 40px auto;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user