parent
fff8fb7d86
commit
46eb9a0303
@ -0,0 +1,29 @@
|
||||
package com.ruoyi.system.api.model;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 角色表 sys_role
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
public class RoleAndPermission implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 角色集合
|
||||
*/
|
||||
private Set<String> roles ;
|
||||
|
||||
/**
|
||||
* 权限集合
|
||||
*/
|
||||
private Set<String> permissions ;
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.ruoyi.system.api.model;
|
||||
|
||||
import com.ruoyi.system.api.domain.SysUser;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class UserInfo implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户基本信息
|
||||
*/
|
||||
private SysUser sysUser;
|
||||
|
||||
/**
|
||||
* 权限标识集合
|
||||
*/
|
||||
private Set<String> permissions;
|
||||
|
||||
/**
|
||||
* 角色集合
|
||||
*/
|
||||
private Set<String> roles;
|
||||
|
||||
public SysUser getSysUser()
|
||||
{
|
||||
return sysUser;
|
||||
}
|
||||
|
||||
public void setSysUser(SysUser sysUser)
|
||||
{
|
||||
this.sysUser = sysUser;
|
||||
}
|
||||
|
||||
public Set<String> getPermissions()
|
||||
{
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public void setPermissions(Set<String> permissions)
|
||||
{
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public Set<String> getRoles()
|
||||
{
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(Set<String> roles)
|
||||
{
|
||||
this.roles = roles;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package com.ruoyi.auth.config;
|
||||
|
||||
import com.ruoyi.common.security.service.UserDetailsServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* Security 安全认证相关配置
|
||||
* Oauth2依赖于Security 默认情况下WebSecurityConfig执行比ResourceServerConfig优先
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Order(99)
|
||||
@Configuration
|
||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
|
||||
{
|
||||
// @Resource(name= "defaultUserDetailsService")
|
||||
// private UserDetailsService userDetailsService;
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder()
|
||||
{
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public AuthenticationManager authenticationManagerBean() throws Exception
|
||||
{
|
||||
return super.authenticationManagerBean();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception
|
||||
{
|
||||
auth.userDetailsService(getUserDetailsService()).passwordEncoder(passwordEncoder());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception
|
||||
{
|
||||
http
|
||||
.authorizeRequests()
|
||||
.antMatchers(
|
||||
"/actuator/**",
|
||||
"/oauth/*",
|
||||
"/token/**",
|
||||
"/logout",
|
||||
"/login"
|
||||
).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and().csrf().disable();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService getUserDetailsService() {
|
||||
return new UserDetailsServiceImpl();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.ruoyi.auth.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
/**
|
||||
* 身份信息获取
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/oauth")
|
||||
public class UserController
|
||||
{
|
||||
@RequestMapping("/user")
|
||||
public Principal user(Principal user)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.ruoyi.auth.exception;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
|
||||
|
||||
/**
|
||||
* oauth2自定义异常
|
||||
*
|
||||
* @author ruoyi
|
||||
**/
|
||||
@JsonSerialize(using = CustomOauthExceptionSerializer.class)
|
||||
public class CustomOauthException extends OAuth2Exception
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public CustomOauthException(String msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.ruoyi.auth.exception;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import com.ruoyi.common.core.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.utils.StringUtils;
|
||||
import com.ruoyi.common.core.web.domain.AjaxResult;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 自定义异常返回
|
||||
*
|
||||
* @author ruoyi
|
||||
**/
|
||||
public class CustomOauthExceptionSerializer extends StdSerializer<CustomOauthException>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CustomOauthExceptionSerializer.class);
|
||||
|
||||
public static final String BAD_CREDENTIALS = "Bad credentials";
|
||||
|
||||
public CustomOauthExceptionSerializer()
|
||||
{
|
||||
super(CustomOauthException.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(CustomOauthException e, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
|
||||
throws IOException
|
||||
{
|
||||
jsonGenerator.writeStartObject();
|
||||
jsonGenerator.writeNumberField(AjaxResult.CODE_TAG, HttpStatus.ERROR);
|
||||
if (StringUtils.equals(e.getMessage(), BAD_CREDENTIALS))
|
||||
{
|
||||
jsonGenerator.writeStringField(AjaxResult.MSG_TAG, "用户名或密码错误");
|
||||
}
|
||||
else
|
||||
{
|
||||
log.warn("oauth2 认证异常 {} ", e);
|
||||
jsonGenerator.writeStringField(AjaxResult.MSG_TAG, e.getMessage());
|
||||
}
|
||||
jsonGenerator.writeEndObject();
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.ruoyi.auth.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
|
||||
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
|
||||
|
||||
/**
|
||||
* OAuth2 自定义异常处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator<OAuth2Exception>
|
||||
{
|
||||
@Override
|
||||
public ResponseEntity<OAuth2Exception> translate(Exception e)
|
||||
{
|
||||
return ResponseEntity.status(HttpStatus.OK).body(new CustomOauthException(e.getMessage()));
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.ruoyi.auth.handler;
|
||||
|
||||
import com.ruoyi.common.core.constant.Constants;
|
||||
import com.ruoyi.common.core.utils.StringUtils;
|
||||
import com.ruoyi.system.api.RemoteLogService;
|
||||
import com.ruoyi.system.api.model.LoginUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 认证成功处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class AuthenticationSuccessEventHandler implements ApplicationListener<AuthenticationSuccessEvent>
|
||||
{
|
||||
@Autowired
|
||||
private RemoteLogService remoteLogService;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AuthenticationSuccessEvent event)
|
||||
{
|
||||
Authentication authentication = (Authentication) event.getSource();
|
||||
if (StringUtils.isNotEmpty(authentication.getAuthorities())
|
||||
&& authentication.getPrincipal() instanceof LoginUser)
|
||||
{
|
||||
LoginUser user = (LoginUser) authentication.getPrincipal();
|
||||
|
||||
String username = user.getUsername();
|
||||
|
||||
// 记录用户登录日志
|
||||
remoteLogService.saveLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,88 +1,91 @@
|
||||
package com.ruoyi.common.core.utils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import com.ruoyi.common.core.constant.CacheConstants;
|
||||
import com.ruoyi.common.core.text.Convert;
|
||||
|
||||
/**
|
||||
* 权限获取工具类
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class SecurityUtils
|
||||
{
|
||||
/**
|
||||
* 获取用户
|
||||
*/
|
||||
public static String getUsername()
|
||||
{
|
||||
String username = ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USERNAME);
|
||||
return ServletUtils.urlDecode(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户ID
|
||||
*/
|
||||
public static Long getUserId()
|
||||
{
|
||||
return Convert.toLong(ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USER_ID));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求token
|
||||
*/
|
||||
public static String getToken()
|
||||
{
|
||||
return getToken(ServletUtils.getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据request获取请求token
|
||||
*/
|
||||
public static String getToken(HttpServletRequest request)
|
||||
{
|
||||
String token = ServletUtils.getRequest().getHeader(CacheConstants.HEADER);
|
||||
if (StringUtils.isNotEmpty(token) && token.startsWith(CacheConstants.TOKEN_PREFIX))
|
||||
{
|
||||
token = token.replace(CacheConstants.TOKEN_PREFIX, "");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为管理员
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isAdmin(Long userId)
|
||||
{
|
||||
return userId != null && 1L == userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成BCryptPasswordEncoder密码
|
||||
*
|
||||
* @param password 密码
|
||||
* @return 加密字符串
|
||||
*/
|
||||
public static String encryptPassword(String password)
|
||||
{
|
||||
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
return passwordEncoder.encode(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断密码是否相同
|
||||
*
|
||||
* @param rawPassword 真实密码
|
||||
* @param encodedPassword 加密后字符
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean matchesPassword(String rawPassword, String encodedPassword)
|
||||
{
|
||||
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
return passwordEncoder.matches(rawPassword, encodedPassword);
|
||||
}
|
||||
}
|
||||
//package com.ruoyi.common.core.utils;
|
||||
//
|
||||
//import javax.servlet.http.HttpServletRequest;
|
||||
//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
//import com.ruoyi.common.core.constant.CacheConstants;
|
||||
//import com.ruoyi.common.core.text.Convert;
|
||||
//
|
||||
///**
|
||||
// * 权限获取工具类
|
||||
// *
|
||||
// * @author ruoyi
|
||||
// */
|
||||
//public class SecurityUtils
|
||||
//{
|
||||
// /**
|
||||
// * 获取用户
|
||||
// */
|
||||
// public static String getUsername()
|
||||
// {
|
||||
// String username = ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USERNAME);
|
||||
// return ServletUtils.urlDecode(username);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取用户ID
|
||||
// */
|
||||
// public static Long getUserId()
|
||||
// {
|
||||
// return Convert.toLong(ServletUtils.getRequest().getHeader(CacheConstants.DETAILS_USER_ID));
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取请求token
|
||||
// */
|
||||
// public static String getToken()
|
||||
// {
|
||||
// return getToken(ServletUtils.getRequest());
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 根据request获取请求token
|
||||
// */
|
||||
// public static String getToken(HttpServletRequest request)
|
||||
// {
|
||||
// String token = ServletUtils.getRequest().getHeader(CacheConstants.HEADER);
|
||||
// if (StringUtils.isNotEmpty(token) && token.startsWith(CacheConstants.TOKEN_PREFIX))
|
||||
// {
|
||||
// token = token.replace(CacheConstants.TOKEN_PREFIX, "");
|
||||
// }
|
||||
// return token;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 是否为管理员
|
||||
// *
|
||||
// * @param userId 用户ID
|
||||
// * @return 结果
|
||||
// */
|
||||
// public static boolean isAdmin(Long userId)
|
||||
// {
|
||||
// return userId != null && 1L == userId;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 生成BCryptPasswordEncoder密码
|
||||
// *
|
||||
// * @param password 密码
|
||||
// * @return 加密字符串
|
||||
// */
|
||||
// public static String encryptPassword(String password)
|
||||
// {
|
||||
// BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
// return passwordEncoder.encode(password);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 判断密码是否相同
|
||||
// *
|
||||
// * @param rawPassword 真实密码
|
||||
// * @param encodedPassword 加密后字符
|
||||
// * @return 结果
|
||||
// */
|
||||
// public static boolean matchesPassword(String rawPassword, String encodedPassword)
|
||||
// {
|
||||
// BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
// return passwordEncoder.matches(rawPassword, encodedPassword);
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
//}
|
||||
|
@ -0,0 +1,31 @@
|
||||
package com.ruoyi.common.security.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Configurable;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 忽略服务间的认证
|
||||
*
|
||||
* @author ruoyi
|
||||
**/
|
||||
@Component
|
||||
@Configurable
|
||||
@ConfigurationProperties(prefix = "security.oauth2.ignore")
|
||||
public class AuthIgnoreConfig
|
||||
{
|
||||
private List<String> urls = new ArrayList<>();
|
||||
|
||||
public List<String> getUrls()
|
||||
{
|
||||
return urls;
|
||||
}
|
||||
|
||||
public void setUrls(List<String> urls)
|
||||
{
|
||||
this.urls = urls;
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package com.ruoyi.common.security.config;
|
||||
|
||||
import com.ruoyi.common.core.constant.SecurityConstants;
|
||||
import com.ruoyi.common.core.text.Convert;
|
||||
import com.ruoyi.system.api.model.LoginUser;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* https://my.oschina.net/giegie/blog/3023768 根据checktoken 的结果转化用户信息
|
||||
*
|
||||
* @author lengleng
|
||||
*/
|
||||
public class CommonUserConverter implements UserAuthenticationConverter
|
||||
{
|
||||
private static final String N_A = "N/A";
|
||||
|
||||
/**
|
||||
* 将授权信息返回到资源服务
|
||||
*/
|
||||
@Override
|
||||
public Map<String, ?> convertUserAuthentication(Authentication userAuthentication)
|
||||
{
|
||||
Map<String, Object> authMap = new LinkedHashMap<>();
|
||||
authMap.put(USERNAME, userAuthentication.getName());
|
||||
if (userAuthentication.getAuthorities() != null && !userAuthentication.getAuthorities().isEmpty())
|
||||
{
|
||||
authMap.put(AUTHORITIES, AuthorityUtils.authorityListToSet(userAuthentication.getAuthorities()));
|
||||
}
|
||||
return authMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户认证信息
|
||||
*/
|
||||
@Override
|
||||
public Authentication extractAuthentication(Map<String, ?> map)
|
||||
{
|
||||
if (map.containsKey(USERNAME))
|
||||
{
|
||||
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
|
||||
Long userId = Convert.toLong(map.get(SecurityConstants.DETAILS_USER_ID));
|
||||
String username = (String) map.get(SecurityConstants.DETAILS_USERNAME);
|
||||
LoginUser user = new LoginUser(userId,username, true, true, true, true, authorities);
|
||||
return new UsernamePasswordAuthenticationToken(user, N_A, authorities);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限资源信息
|
||||
*/
|
||||
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map)
|
||||
{
|
||||
Object authorities = map.get(AUTHORITIES);
|
||||
if (authorities instanceof String)
|
||||
{
|
||||
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
|
||||
}
|
||||
if (authorities instanceof Collection)
|
||||
{
|
||||
return AuthorityUtils.commaSeparatedStringToAuthorityList(
|
||||
StringUtils.collectionToCommaDelimitedString((Collection<?>) authorities));
|
||||
}
|
||||
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.ruoyi.common.security.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
|
||||
/**
|
||||
*
|
||||
* @EnableGlobalMethodSecurity(securedEnabled=true)
|
||||
* 开启@Secured 注解过滤权限
|
||||
*
|
||||
* @EnableGlobalMethodSecurity(jsr250Enabled=true)
|
||||
* 开启@RolesAllowed 注解过滤权限
|
||||
*
|
||||
* @EnableGlobalMethodSecurity(prePostEnabled=true)
|
||||
* 使用表达式时间方法级别的安全性 4个注解可用
|
||||
* -@PreAuthorize 在方法调用之前,基于表达式的计算结果来限制对方法的访问
|
||||
* -@PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常
|
||||
* -@PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果
|
||||
* -@PreFilter 允许方法调用,但必须在进入方法之前过滤输入值
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class MethodSecurityConfig {
|
||||
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package com.ruoyi.common.security.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2ClientProperties;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
|
||||
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
|
||||
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
|
||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* oauth2 服务配置
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
@EnableResourceServer
|
||||
public class ResourceServerConfig extends ResourceServerConfigurerAdapter
|
||||
{
|
||||
@Autowired
|
||||
private ResourceServerProperties resourceServerProperties;
|
||||
|
||||
@Autowired
|
||||
private OAuth2ClientProperties oAuth2ClientProperties;
|
||||
|
||||
@Bean
|
||||
public AuthIgnoreConfig authIgnoreConfig()
|
||||
{
|
||||
return new AuthIgnoreConfig();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
public RestTemplate restTemplate()
|
||||
{
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ResourceServerTokenServices tokenServices()
|
||||
{
|
||||
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
|
||||
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
|
||||
UserAuthenticationConverter userTokenConverter = new CommonUserConverter();
|
||||
accessTokenConverter.setUserTokenConverter(userTokenConverter);
|
||||
remoteTokenServices.setCheckTokenEndpointUrl(resourceServerProperties.getTokenInfoUri());
|
||||
remoteTokenServices.setClientId(oAuth2ClientProperties.getClientId());
|
||||
remoteTokenServices.setClientSecret(oAuth2ClientProperties.getClientSecret());
|
||||
remoteTokenServices.setRestTemplate(restTemplate());
|
||||
remoteTokenServices.setAccessTokenConverter(accessTokenConverter);
|
||||
return remoteTokenServices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity http) throws Exception
|
||||
{
|
||||
http.csrf().disable();
|
||||
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
|
||||
.authorizeRequests();
|
||||
// 不登录可以访问
|
||||
authIgnoreConfig().getUrls().forEach(url -> registry.antMatchers(url).permitAll());
|
||||
registry.anyRequest().authenticated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(ResourceServerSecurityConfigurer resources)
|
||||
{
|
||||
resources.tokenServices(tokenServices());
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.ruoyi.common.security.config;
|
||||
|
||||
import com.ruoyi.common.core.utils.StringUtils;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* 导入 SecurityImportBeanDefinitionRegistrar 自动加载类
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class SecurityImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar
|
||||
{
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)
|
||||
{
|
||||
Class<ResourceServerConfig> aClass = ResourceServerConfig.class;
|
||||
String beanName = StringUtils.uncapitalize(aClass.getSimpleName());
|
||||
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ResourceServerConfig.class);
|
||||
registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
|
||||
}
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
package com.ruoyi.common.security.feign;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import feign.RequestInterceptor;
|
||||
|
||||
/**
|
||||
* Feign 配置注册
|
||||
*
|
||||
* @author ruoyi
|
||||
**/
|
||||
@Configuration
|
||||
public class FeignAutoConfiguration
|
||||
{
|
||||
@Bean
|
||||
public RequestInterceptor requestInterceptor()
|
||||
{
|
||||
return new FeignRequestInterceptor();
|
||||
}
|
||||
}
|
||||
//package com.ruoyi.common.security.feign;
|
||||
//
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import feign.RequestInterceptor;
|
||||
//
|
||||
///**
|
||||
// * Feign 配置注册
|
||||
// *
|
||||
// * @author ruoyi
|
||||
// **/
|
||||
//@Configuration
|
||||
//public class FeignAutoConfiguration
|
||||
//{
|
||||
// @Bean
|
||||
// public RequestInterceptor requestInterceptor()
|
||||
// {
|
||||
// return new FeignRequestInterceptor();
|
||||
// }
|
||||
//}
|
||||
|
@ -0,0 +1,20 @@
|
||||
package com.ruoyi.common.security.feign;
|
||||
|
||||
import feign.RequestInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Feign配置注册
|
||||
*
|
||||
* @author ruoyi
|
||||
**/
|
||||
@Configuration
|
||||
public class FeignConfig
|
||||
{
|
||||
@Bean
|
||||
public RequestInterceptor requestInterceptor()
|
||||
{
|
||||
return new OAuth2FeignRequestInterceptor();
|
||||
}
|
||||
}
|
@ -1,45 +1,45 @@
|
||||
package com.ruoyi.common.security.feign;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.ruoyi.common.core.constant.CacheConstants;
|
||||
import com.ruoyi.common.core.utils.ServletUtils;
|
||||
import com.ruoyi.common.core.utils.StringUtils;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
|
||||
/**
|
||||
* feign 请求拦截器
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class FeignRequestInterceptor implements RequestInterceptor
|
||||
{
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate)
|
||||
{
|
||||
HttpServletRequest httpServletRequest = ServletUtils.getRequest();
|
||||
if (StringUtils.isNotNull(httpServletRequest))
|
||||
{
|
||||
Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
|
||||
// 传递用户信息请求头,防止丢失
|
||||
String userId = headers.get(CacheConstants.DETAILS_USER_ID);
|
||||
if (StringUtils.isNotEmpty(userId))
|
||||
{
|
||||
requestTemplate.header(CacheConstants.DETAILS_USER_ID, userId);
|
||||
}
|
||||
String userName = headers.get(CacheConstants.DETAILS_USERNAME);
|
||||
if (StringUtils.isNotEmpty(userName))
|
||||
{
|
||||
requestTemplate.header(CacheConstants.DETAILS_USERNAME, userName);
|
||||
}
|
||||
String authentication = headers.get(CacheConstants.AUTHORIZATION_HEADER);
|
||||
if (StringUtils.isNotEmpty(authentication))
|
||||
{
|
||||
requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER, authentication);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//package com.ruoyi.common.security.feign;
|
||||
//
|
||||
//import java.util.Map;
|
||||
//import javax.servlet.http.HttpServletRequest;
|
||||
//import org.springframework.stereotype.Component;
|
||||
//import com.ruoyi.common.core.constant.CacheConstants;
|
||||
//import com.ruoyi.common.core.utils.ServletUtils;
|
||||
//import com.ruoyi.common.core.utils.StringUtils;
|
||||
//import feign.RequestInterceptor;
|
||||
//import feign.RequestTemplate;
|
||||
//
|
||||
///**
|
||||
// * feign 请求拦截器
|
||||
// *
|
||||
// * @author ruoyi
|
||||
// */
|
||||
//@Component
|
||||
//public class FeignRequestInterceptor implements RequestInterceptor
|
||||
//{
|
||||
// @Override
|
||||
// public void apply(RequestTemplate requestTemplate)
|
||||
// {
|
||||
// HttpServletRequest httpServletRequest = ServletUtils.getRequest();
|
||||
// if (StringUtils.isNotNull(httpServletRequest))
|
||||
// {
|
||||
// Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
|
||||
// // 传递用户信息请求头,防止丢失
|
||||
// String userId = headers.get(CacheConstants.DETAILS_USER_ID);
|
||||
// if (StringUtils.isNotEmpty(userId))
|
||||
// {
|
||||
// requestTemplate.header(CacheConstants.DETAILS_USER_ID, userId);
|
||||
// }
|
||||
// String userName = headers.get(CacheConstants.DETAILS_USERNAME);
|
||||
// if (StringUtils.isNotEmpty(userName))
|
||||
// {
|
||||
// requestTemplate.header(CacheConstants.DETAILS_USERNAME, userName);
|
||||
// }
|
||||
// String authentication = headers.get(CacheConstants.AUTHORIZATION_HEADER);
|
||||
// if (StringUtils.isNotEmpty(authentication))
|
||||
// {
|
||||
// requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER, authentication);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
@ -0,0 +1,33 @@
|
||||
package com.ruoyi.common.security.feign;
|
||||
|
||||
import com.ruoyi.common.core.constant.SecurityConstants;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* feign 请求拦截器
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class OAuth2FeignRequestInterceptor implements RequestInterceptor
|
||||
{
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate)
|
||||
{
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
Authentication authentication = securityContext.getAuthentication();
|
||||
if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails)
|
||||
{
|
||||
OAuth2AuthenticationDetails dateils = (OAuth2AuthenticationDetails) authentication.getDetails();
|
||||
requestTemplate.header(HttpHeaders.AUTHORIZATION,
|
||||
String.format("%s %s", SecurityConstants.BEARER_TOKEN_TYPE, dateils.getTokenValue()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.ruoyi.common.security.handler;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.ruoyi.common.core.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.utils.ServletUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 自定义访问无权限资源时的异常
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class CustomAccessDeniedHandler extends OAuth2AccessDeniedHandler
|
||||
{
|
||||
private final Logger logger = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authException)
|
||||
{
|
||||
logger.info("权限不足,请联系管理员 {}", request.getRequestURI());
|
||||
|
||||
String msg = authException.getMessage();
|
||||
ServletUtils.renderString(response, JSON.toJSONString(R.fail(HttpStatus.FORBIDDEN, msg)));
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.ruoyi.common.security.handler;
|
||||
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.system.api.RemoteUserService;
|
||||
import com.ruoyi.system.api.model.LoginUser;
|
||||
import com.ruoyi.system.api.model.UserInfo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author alikes
|
||||
*/
|
||||
@Component
|
||||
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {
|
||||
|
||||
@Autowired
|
||||
private RemoteUserService remoteUserService;
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
|
||||
if(authentication instanceof LoginUser){
|
||||
LoginUser loginUser = (LoginUser) authentication;
|
||||
R<UserInfo> res = remoteUserService.getUserInfo(loginUser.getSysUser().getUserName());
|
||||
loginUser.setRoles(res.getData().getRoles());
|
||||
loginUser.setPermissions(res.getData().getPermissions());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.ruoyi.common.security.service;
|
||||
|
||||
import com.ruoyi.common.core.constant.CacheConstants;
|
||||
import com.ruoyi.common.core.constant.SecurityConstants;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* 重写原生方法支持redis缓存
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class RedisClientDetailsService extends JdbcClientDetailsService
|
||||
{
|
||||
public RedisClientDetailsService(DataSource dataSource)
|
||||
{
|
||||
super(dataSource);
|
||||
super.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
|
||||
super.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
|
||||
public ClientDetails loadClientByClientId(String clientId)
|
||||
{
|
||||
return super.loadClientByClientId(clientId);
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package com.ruoyi.common.security.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.enums.UserStatus;
|
||||
import com.ruoyi.common.core.exception.BaseException;
|
||||
import com.ruoyi.common.core.utils.StringUtils;
|
||||
import com.ruoyi.system.api.RemoteUserService;
|
||||
import com.ruoyi.system.api.domain.SysUser;
|
||||
import com.ruoyi.system.api.model.LoginUser;
|
||||
import com.ruoyi.system.api.model.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 用户信息处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service("defaultUserDetailsService")
|
||||
public class UserDetailsServiceImpl implements UserDetailsService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private RemoteUserService remoteUserService;
|
||||
|
||||
@Override
|
||||
public LoginUser loadUserByUsername(String username)
|
||||
{
|
||||
R<UserInfo> userResult = remoteUserService.getUserInfo(username);
|
||||
checkUser(userResult, username);
|
||||
return getUserDetails(userResult);
|
||||
}
|
||||
|
||||
public void checkUser(R<UserInfo> userResult, String username)
|
||||
{
|
||||
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
|
||||
{
|
||||
log.info("登录用户:{} 不存在.", username);
|
||||
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
|
||||
}
|
||||
else if (UserStatus.DELETED.getCode().equals(userResult.getData().getSysUser().getDelFlag()))
|
||||
{
|
||||
log.info("登录用户:{} 已被删除.", username);
|
||||
throw new BaseException("对不起,您的账号:" + username + " 已被删除");
|
||||
}
|
||||
else if (UserStatus.DISABLE.getCode().equals(userResult.getData().getSysUser().getStatus()))
|
||||
{
|
||||
log.info("登录用户:{} 已被停用.", username);
|
||||
throw new BaseException("对不起,您的账号:" + username + " 已停用");
|
||||
}
|
||||
}
|
||||
|
||||
private LoginUser getUserDetails(R<UserInfo> result)
|
||||
{
|
||||
UserInfo info = result.getData();
|
||||
Set<String> dbAuthsSet = new HashSet<String>();
|
||||
if (StringUtils.isNotEmpty(info.getRoles()))
|
||||
{
|
||||
// 获取角色
|
||||
dbAuthsSet.addAll(info.getRoles());
|
||||
// 获取权限
|
||||
dbAuthsSet.addAll(info.getPermissions());
|
||||
}
|
||||
|
||||
Collection<? extends GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(dbAuthsSet.toArray(new String[0]));
|
||||
SysUser user = info.getSysUser();
|
||||
LoginUser loginUser = new LoginUser(user.getUserId(), user.getUserName(), user.getPassword(), true, true, true, true, authorities);
|
||||
loginUser.setPermissions(info.getPermissions());
|
||||
loginUser.setRoles(info.getRoles());
|
||||
return loginUser;
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package com.ruoyi.common.security.utils;
|
||||
|
||||
import com.ruoyi.common.core.text.Convert;
|
||||
import com.ruoyi.system.api.model.LoginUser;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
/**
|
||||
* 权限获取工具类
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class SecurityUtils
|
||||
{
|
||||
/**
|
||||
* 获取Authentication
|
||||
*/
|
||||
public static Authentication getAuthentication()
|
||||
{
|
||||
return SecurityContextHolder.getContext().getAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户
|
||||
*/
|
||||
public static String getUsername()
|
||||
{
|
||||
return getLoginUser().getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户
|
||||
*/
|
||||
public static LoginUser getLoginUser(Authentication authentication)
|
||||
{
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (principal instanceof LoginUser)
|
||||
{
|
||||
return (LoginUser) principal;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取用户ID
|
||||
*/
|
||||
public static Long getUserId(){
|
||||
return Convert.toLong(getLoginUser().getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户
|
||||
*/
|
||||
public static LoginUser getLoginUser()
|
||||
{
|
||||
Authentication authentication = getAuthentication();
|
||||
if (authentication == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return getLoginUser(authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成BCryptPasswordEncoder密码
|
||||
*
|
||||
* @param password 密码
|
||||
* @return 加密字符串
|
||||
*/
|
||||
public static String encryptPassword(String password)
|
||||
{
|
||||
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
return passwordEncoder.encode(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断密码是否相同
|
||||
*
|
||||
* @param rawPassword 真实密码
|
||||
* @param encodedPassword 加密后字符
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean matchesPassword(String rawPassword, String encodedPassword)
|
||||
{
|
||||
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
return passwordEncoder.matches(rawPassword, encodedPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为管理员
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isAdmin(Long userId)
|
||||
{
|
||||
return userId != null && 1L == userId;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.alibaba.csp.sentinel.dashboard.controller.v2;
|
||||
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
|
||||
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 控制器基类
|
||||
* @author alikes
|
||||
*/
|
||||
public class BaseController {
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
|
||||
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
|
||||
import com.alibaba.csp.sentinel.datasource.Converter;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.nacos.api.config.ConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 降级规则
|
||||
* @author alikes
|
||||
*/
|
||||
@Component("degradeRuleNacosProvider")
|
||||
public class DegradeRuleNacosProvider implements DynamicRuleProvider<List<DegradeRuleEntity>> {
|
||||
|
||||
@Autowired
|
||||
private ConfigService configService;
|
||||
|
||||
@Autowired
|
||||
private Converter<String, List<DegradeRuleEntity>> converter;
|
||||
|
||||
@Override
|
||||
public List<DegradeRuleEntity> getRules(String appName) throws Exception {
|
||||
String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
|
||||
NacosConfigUtil.GROUP_ID, 3000);
|
||||
if (StringUtil.isEmpty(rules)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return converter.convert(rules);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.csp.sentinel.dashboard.rule.nacos;
|
||||
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
|
||||
import com.alibaba.csp.sentinel.datasource.Converter;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.nacos.api.config.ConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Component("degradeRuleNacosPublisher")
|
||||
public class DegradeRuleNacosPublisher implements DynamicRulePublisher<List<DegradeRuleEntity>> {
|
||||
|
||||
@Autowired
|
||||
private ConfigService configService;
|
||||
@Autowired
|
||||
private Converter<List<DegradeRuleEntity>, String> converter;
|
||||
|
||||
@Override
|
||||
public void publish(String app, List<DegradeRuleEntity> rules) throws Exception {
|
||||
AssertUtil.notEmpty(app, "app name cannot be empty");
|
||||
if (rules == null) {
|
||||
return;
|
||||
}
|
||||
configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
|
||||
NacosConfigUtil.GROUP_ID, converter.convert(rules));
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
#spring settings
|
||||
spring.http.encoding.force=true
|
||||
spring.http.encoding.charset=UTF-8
|
||||
spring.http.encoding.enabled=true
|
||||
|
||||
#cookie name setting
|
||||
server.servlet.session.cookie.name=sentinel_dashboard_cookie
|
||||
|
||||
#logging settings
|
||||
logging.level.org.springframework.web=INFO
|
||||
logging.file=${user.home}/logs/csp/sentinel-dashboard.log
|
||||
logging.pattern.file= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
|
||||
#logging.pattern.console= %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
|
||||
|
||||
#auth settings
|
||||
auth.filter.exclude-urls=/,/auth/login,/auth/logout,/registry/machine,/version
|
||||
auth.filter.exclude-url-suffixes=htm,html,js,css,map,ico,ttf,woff,png
|
||||
# If auth.enabled=false, Sentinel console disable login
|
||||
auth.username=sentinel
|
||||
auth.password=sentinel
|
||||
|
||||
# Inject the dashboard version. It's required to enable
|
||||
# filtering in pom.xml for this resource file.
|
||||
sentinel.dashboard.version=${project.version}
|
||||
spring.nacos.server-addr=127.0.0.1:8848
|
||||
server.port=8718
|
@ -0,0 +1,50 @@
|
||||
## Tomcat
|
||||
#server:
|
||||
# port: 8718
|
||||
# servlet:
|
||||
# encoding:
|
||||
# enabled: true
|
||||
# force: true
|
||||
# charset: UTF-8
|
||||
# session:
|
||||
# cookie:
|
||||
# name: sentinel_dashboard_cookie
|
||||
#logging:
|
||||
# level:
|
||||
# org:
|
||||
# springframework:
|
||||
# web: INFO
|
||||
# file:
|
||||
# name: ${user.home}/logs/csp/sentinel-dashboard.log
|
||||
# pattern:
|
||||
# file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||
#auth:
|
||||
# filter:
|
||||
# exclude-urls: "/,/auth/login,/auth/logout,/registry/machine,/version"
|
||||
# exclude-url-suffixes: "htm,html,js,css,map,ico,ttf,woff,png"
|
||||
# username: sentinel
|
||||
# password: sentinel
|
||||
#sentinel:
|
||||
# dashboard:
|
||||
# version: ${project.version}
|
||||
## Spring
|
||||
#spring:
|
||||
# application:
|
||||
# # 应用名称
|
||||
# name: ruoyi-sentinel
|
||||
# profiles:
|
||||
# # 环境配置
|
||||
# active: dev
|
||||
# cloud:
|
||||
# nacos:
|
||||
# discovery:
|
||||
# # 服务注册地址
|
||||
# server-addr: 175.25.50.135:8848
|
||||
# config:
|
||||
# # 配置中心地址
|
||||
# server-addr: 175.25.50.135:8848
|
||||
# # 配置文件格式
|
||||
# file-extension: yml
|
||||
# # 共享配置
|
||||
# shared-configs:
|
||||
# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
|
@ -0,0 +1,24 @@
|
||||
sentinel:
|
||||
dashboard:
|
||||
version: ${project.version}
|
||||
# Spring
|
||||
spring:
|
||||
application:
|
||||
# 应用名称
|
||||
name: ruoyi-sentinel
|
||||
profiles:
|
||||
# 环境配置
|
||||
active: dev
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
# 服务注册地址
|
||||
server-addr: 175.25.50.135:8848
|
||||
config:
|
||||
# 配置中心地址
|
||||
server-addr: 175.25.50.135:8848
|
||||
# 配置文件格式
|
||||
file-extension: yml
|
||||
# 共享配置
|
||||
shared-configs:
|
||||
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
|
@ -0,0 +1,204 @@
|
||||
var app = angular.module('sentinelDashboardApp');
|
||||
|
||||
app.controller('DegradeCtl', ['$scope', '$stateParams', 'DegradeService', 'ngDialog', 'MachineService',
|
||||
function ($scope, $stateParams, DegradeService, ngDialog, MachineService) {
|
||||
//初始化
|
||||
$scope.app = $stateParams.app;
|
||||
$scope.rulesPageConfig = {
|
||||
pageSize: 10,
|
||||
currentPageIndex: 1,
|
||||
totalPage: 1,
|
||||
totalCount: 0,
|
||||
};
|
||||
$scope.macsInputConfig = {
|
||||
searchField: ['text', 'value'],
|
||||
persist: true,
|
||||
create: false,
|
||||
maxItems: 1,
|
||||
render: {
|
||||
item: function (data, escape) {
|
||||
return '<div>' + escape(data.text) + '</div>';
|
||||
}
|
||||
},
|
||||
onChange: function (value, oldValue) {
|
||||
$scope.macInputModel = value;
|
||||
}
|
||||
};
|
||||
getMachineRules();
|
||||
function getMachineRules() {
|
||||
if (!$scope.macInputModel) {
|
||||
return;
|
||||
}
|
||||
var mac = $scope.macInputModel.split(':');
|
||||
DegradeService.queryMachineRules($scope.app, mac[0], mac[1]).success(
|
||||
function (data) {
|
||||
if (data.code == 0 && data.data) {
|
||||
$scope.rules = data.data;
|
||||
$scope.rulesPageConfig.totalCount = $scope.rules.length;
|
||||
} else {
|
||||
$scope.rules = [];
|
||||
$scope.rulesPageConfig.totalCount = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
$scope.getMachineRules = getMachineRules;
|
||||
|
||||
var degradeRuleDialog;
|
||||
$scope.editRule = function (rule) {
|
||||
$scope.currentRule = angular.copy(rule);
|
||||
$scope.degradeRuleDialog = {
|
||||
title: '编辑降级规则',
|
||||
type: 'edit',
|
||||
confirmBtnText: '保存'
|
||||
};
|
||||
degradeRuleDialog = ngDialog.open({
|
||||
template: '/app/views/dialog/degrade-rule-dialog.html',
|
||||
width: 680,
|
||||
overlay: true,
|
||||
scope: $scope
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addNewRule = function () {
|
||||
var mac = $scope.macInputModel.split(':');
|
||||
$scope.currentRule = {
|
||||
grade: 0,
|
||||
app: $scope.app,
|
||||
ip: mac[0],
|
||||
port: mac[1],
|
||||
limitApp: 'default',
|
||||
minRequestAmount: 5,
|
||||
statIntervalMs: 1000,
|
||||
};
|
||||
$scope.degradeRuleDialog = {
|
||||
title: '新增降级规则',
|
||||
type: 'add',
|
||||
confirmBtnText: '新增'
|
||||
};
|
||||
degradeRuleDialog = ngDialog.open({
|
||||
template: '/app/views/dialog/degrade-rule-dialog.html',
|
||||
width: 680,
|
||||
overlay: true,
|
||||
scope: $scope
|
||||
});
|
||||
};
|
||||
|
||||
$scope.saveRule = function () {
|
||||
if (!DegradeService.checkRuleValid($scope.currentRule)) {
|
||||
return;
|
||||
}
|
||||
if ($scope.degradeRuleDialog.type === 'add') {
|
||||
addNewRule($scope.currentRule);
|
||||
} else if ($scope.degradeRuleDialog.type === 'edit') {
|
||||
saveRule($scope.currentRule, true);
|
||||
}
|
||||
};
|
||||
|
||||
function parseDegradeMode(grade) {
|
||||
switch (grade) {
|
||||
case 0:
|
||||
return '慢调用比例';
|
||||
case 1:
|
||||
return '异常比例';
|
||||
case 2:
|
||||
return '异常数';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
}
|
||||
|
||||
var confirmDialog;
|
||||
$scope.deleteRule = function (rule) {
|
||||
$scope.currentRule = rule;
|
||||
$scope.confirmDialog = {
|
||||
title: '删除降级规则',
|
||||
type: 'delete_rule',
|
||||
attentionTitle: '请确认是否删除如下降级规则',
|
||||
attention: '资源名: ' + rule.resource +
|
||||
', 降级模式: ' + parseDegradeMode(rule.grade) + ', 阈值: ' + rule.count,
|
||||
confirmBtnText: '删除',
|
||||
};
|
||||
confirmDialog = ngDialog.open({
|
||||
template: '/app/views/dialog/confirm-dialog.html',
|
||||
scope: $scope,
|
||||
overlay: true
|
||||
});
|
||||
};
|
||||
|
||||
$scope.confirm = function () {
|
||||
if ($scope.confirmDialog.type == 'delete_rule') {
|
||||
deleteRule($scope.currentRule);
|
||||
} else {
|
||||
console.error('error');
|
||||
}
|
||||
};
|
||||
|
||||
function deleteRule(rule) {
|
||||
DegradeService.deleteRule(rule).success(function (data) {
|
||||
if (data.code == 0) {
|
||||
getMachineRules();
|
||||
confirmDialog.close();
|
||||
} else {
|
||||
alert('失败:' + data.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function addNewRule(rule) {
|
||||
DegradeService.newRule(rule).success(function (data) {
|
||||
if (data.code == 0) {
|
||||
getMachineRules();
|
||||
degradeRuleDialog.close();
|
||||
} else {
|
||||
alert('失败:' + data.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function saveRule(rule, edit) {
|
||||
DegradeService.saveRule(rule).success(function (data) {
|
||||
if (data.code == 0) {
|
||||
getMachineRules();
|
||||
if (edit) {
|
||||
degradeRuleDialog.close();
|
||||
} else {
|
||||
confirmDialog.close();
|
||||
}
|
||||
} else {
|
||||
alert('失败:' + data.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
queryAppMachines();
|
||||
function queryAppMachines() {
|
||||
MachineService.getAppMachines($scope.app).success(
|
||||
function (data) {
|
||||
if (data.code == 0) {
|
||||
// $scope.machines = data.data;
|
||||
if (data.data) {
|
||||
$scope.machines = [];
|
||||
$scope.macsInputOptions = [];
|
||||
data.data.forEach(function (item) {
|
||||
if (item.healthy) {
|
||||
$scope.macsInputOptions.push({
|
||||
text: item.ip + ':' + item.port,
|
||||
value: item.ip + ':' + item.port
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($scope.macsInputOptions.length > 0) {
|
||||
$scope.macInputModel = $scope.macsInputOptions[0].value;
|
||||
}
|
||||
} else {
|
||||
$scope.macsInputOptions = [];
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
$scope.$watch('macInputModel', function () {
|
||||
if ($scope.macInputModel) {
|
||||
getMachineRules();
|
||||
}
|
||||
});
|
||||
}]);
|
@ -0,0 +1,97 @@
|
||||
var app = angular.module('sentinelDashboardApp');
|
||||
|
||||
app.service('DegradeService', ['$http', function ($http) {
|
||||
this.queryMachineRules = function (app, ip, port) {
|
||||
var param = {
|
||||
app: app,
|
||||
ip: ip,
|
||||
port: port
|
||||
};
|
||||
return $http({
|
||||
url: '/V2/degrade/rules',
|
||||
params: param,
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
this.newRule = function (rule) {
|
||||
return $http({
|
||||
url: '/degrade/rule',
|
||||
data: rule,
|
||||
method: 'POST'
|
||||
});
|
||||
};
|
||||
|
||||
this.saveRule = function (rule) {
|
||||
var param = {
|
||||
id: rule.id,
|
||||
resource: rule.resource,
|
||||
limitApp: rule.limitApp,
|
||||
grade: rule.grade,
|
||||
count: rule.count,
|
||||
timeWindow: rule.timeWindow,
|
||||
statIntervalMs: rule.statIntervalMs,
|
||||
minRequestAmount: rule.minRequestAmount,
|
||||
slowRatioThreshold: rule.slowRatioThreshold,
|
||||
};
|
||||
return $http({
|
||||
url: '/degrade/rule/' + rule.id,
|
||||
data: param,
|
||||
method: 'PUT'
|
||||
});
|
||||
};
|
||||
|
||||
this.deleteRule = function (rule) {
|
||||
return $http({
|
||||
url: '/degrade/rule/' + rule.id,
|
||||
method: 'DELETE'
|
||||
});
|
||||
};
|
||||
|
||||
this.checkRuleValid = function (rule) {
|
||||
if (rule.resource === undefined || rule.resource === '') {
|
||||
alert('资源名称不能为空');
|
||||
return false;
|
||||
}
|
||||
if (rule.grade === undefined || rule.grade < 0) {
|
||||
alert('未知的降级策略');
|
||||
return false;
|
||||
}
|
||||
if (rule.count === undefined || rule.count === '' || rule.count < 0) {
|
||||
alert('降级阈值不能为空或小于 0');
|
||||
return false;
|
||||
}
|
||||
if (rule.timeWindow == undefined || rule.timeWindow === '' || rule.timeWindow <= 0) {
|
||||
alert('熔断时长必须大于 0s');
|
||||
return false;
|
||||
}
|
||||
if (rule.minRequestAmount == undefined || rule.minRequestAmount <= 0) {
|
||||
alert('最小请求数目需大于 0');
|
||||
return false;
|
||||
}
|
||||
if (rule.statIntervalMs == undefined || rule.statIntervalMs <= 0) {
|
||||
alert('统计窗口时长需大于 0s');
|
||||
return false;
|
||||
}
|
||||
if (rule.statIntervalMs !== undefined && rule.statIntervalMs > 60 * 1000 * 2) {
|
||||
alert('统计窗口时长不能超过 120 分钟');
|
||||
return false;
|
||||
}
|
||||
// 异常比率类型.
|
||||
if (rule.grade == 1 && rule.count > 1) {
|
||||
alert('异常比率超出范围:[0.0 - 1.0]');
|
||||
return false;
|
||||
}
|
||||
if (rule.grade == 0) {
|
||||
if (rule.slowRatioThreshold == undefined) {
|
||||
alert('慢调用比率不能为空');
|
||||
return false;
|
||||
}
|
||||
if (rule.slowRatioThreshold < 0 || rule.slowRatioThreshold > 1) {
|
||||
alert('慢调用比率超出范围:[0.0 - 1.0]');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}]);
|
@ -0,0 +1,98 @@
|
||||
<div class="row" style="margin-left: 1px; margin-top:10px; height: 50px;">
|
||||
<div class="col-md-6" style="margin-bottom: 10px;">
|
||||
<span style="font-size: 30px;font-weight: bold;">{{app}}</span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<button class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ng-disabled="!macInputModel" ng-click="addNewRule()">
|
||||
<i class="fa fa-plus"></i> 新增降级规则</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row" style="margin-top: 20px; margin-bottom: 20px;">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="inputs-header">
|
||||
<span class="brand" style="font-size: 13px;">降级规则</span>
|
||||
<!--<button class="btn btn-danger" style="float: right;margin-right: 10px;height: 30px;font-size: 12px;" ng-click="disableAll()">全部禁用</button>-->
|
||||
<button class="btn btn-primary" style="float: right; margin-right: 10px; height: 30px;font-size: 12px;" ng-click="getMachineRules()">刷新</button>
|
||||
<input class="form-control witdh-200" placeholder="关键字" ng-model="searchKey">
|
||||
<div class="control-group" style="float:right;margin-right: 10px;margin-bottom: -10px;">
|
||||
<selectize id="gsInput" class="selectize-input-200" config="macsInputConfig" options="macsInputOptions" ng-model="macInputModel"
|
||||
placeholder="机器"></selectize>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--.tools-header -->
|
||||
<div class="card-body" style="padding: 0px 0px;">
|
||||
<table class="table" style="border-left: none; border-right:none;margin-top: 10px;">
|
||||
<thead>
|
||||
<tr style="background: #F3F5F7;">
|
||||
<td style="width: 40%">
|
||||
资源名
|
||||
</td>
|
||||
<td style="width: 10%;">
|
||||
降级策略
|
||||
</td>
|
||||
<td style="width: 10%;">
|
||||
阈值
|
||||
</td>
|
||||
<td style="width: 10%;">
|
||||
熔断时长(s)
|
||||
</td>
|
||||
<td style="width: 12%;">
|
||||
操作
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="rule in rules | filter : searchKey | itemsPerPage: rulesPageConfig.pageSize " current-page="rulesPageConfig.currentPageIndex"
|
||||
pagination-id="entriesPagination">
|
||||
<td style="word-wrap:break-word;word-break:break-all;">{{rule.resource}}</td>
|
||||
<!--<td style="word-wrap:break-word;word-break:break-all;">{{rule.limitApp }}</td>-->
|
||||
<td>
|
||||
<span ng-if="rule.grade == 0">慢调用比例</span>
|
||||
<span ng-if="rule.grade == 1" title="异常比例">异常比例</span>
|
||||
<span ng-if="rule.grade == 2" title="异常数">异常数</span>
|
||||
</td>
|
||||
<td style="word-wrap:break-word;word-break:break-all;">
|
||||
{{rule.count}}
|
||||
</td>
|
||||
<td style="word-wrap:break-word;word-break:break-all;">
|
||||
{{rule.timeWindow}}s
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<button class="btn btn-xs btn-default" type="button" ng-click="editRule(rule)" style="font-size: 12px; height:25px;">编辑</button>
|
||||
<button class="btn btn-xs btn-default" type="button" ng-click="deleteRule(rule)" style="font-size: 12px; height:25px;">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- .card-body -->
|
||||
<div class="pagination-footer">
|
||||
<dir-pagination-controls boundary-links="true" template-url="app/views/pagination.tpl.html" pagination-id="entriesPagination"
|
||||
on-page-change="">
|
||||
</dir-pagination-controls>
|
||||
<div class="tools" style="">
|
||||
<span>共 {{rulesPageConfig.totalCount}} 条记录, </span>
|
||||
<span>
|
||||
每页
|
||||
<input class="form-control" ng-model="rulesPageConfig.pageSize"> 条记录,
|
||||
</span>
|
||||
<span>第 {{rulesPageConfig.currentPageIndex}} / {{rulesPageConfig.totalPage}} 页</span>
|
||||
</div>
|
||||
<!-- .tools -->
|
||||
</div>
|
||||
<!-- pagination-footer -->
|
||||
</div>
|
||||
<!-- .card -->
|
||||
</div>
|
||||
<!-- .col-md-12 -->
|
||||
</div>
|
||||
<!-- -->
|
||||
</div>
|
||||
<!-- .container-fluid -->
|
File diff suppressed because it is too large
Load Diff
@ -1,46 +1,90 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
const client_id = 'web'
|
||||
const client_secret = '123456'
|
||||
const scope = 'server'
|
||||
|
||||
// 登录方法
|
||||
export function login(username, password, code, uuid) {
|
||||
return request({
|
||||
url: '/auth/login',
|
||||
method: 'post',
|
||||
data: { username, password, code, uuid }
|
||||
})
|
||||
}
|
||||
|
||||
// 刷新方法
|
||||
export function refreshToken() {
|
||||
return request({
|
||||
url: '/auth/refresh',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取用户详细信息
|
||||
export function getInfo() {
|
||||
return request({
|
||||
url: '/system/user/getInfo',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 退出方法
|
||||
export function logout() {
|
||||
return request({
|
||||
url: '/auth/logout',
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取验证码
|
||||
export function getCodeImg() {
|
||||
return request({
|
||||
url: '/code',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
import request from '@/utils/request'
|
||||
|
||||
const client_id = 'web'
|
||||
const client_secret = '123456'
|
||||
const scope = 'server'
|
||||
|
||||
// // 登录方法
|
||||
// export function login(username, password, code, uuid) {
|
||||
// return request({
|
||||
// url: '/auth/login',
|
||||
// method: 'post',
|
||||
// data: { username, password, code, uuid }
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// // 刷新方法
|
||||
// export function refreshToken() {
|
||||
// return request({
|
||||
// url: '/auth/refresh',
|
||||
// method: 'post'
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// // 获取用户详细信息
|
||||
// export function getInfo() {
|
||||
// return request({
|
||||
// url: '/system/user/getInfo',
|
||||
// method: 'get'
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// // 退出方法
|
||||
// export function logout() {
|
||||
// return request({
|
||||
// url: '/auth/logout',
|
||||
// method: 'delete'
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// // 获取验证码
|
||||
// export function getCodeImg() {
|
||||
// return request({
|
||||
// url: '/code',
|
||||
// method: 'get'
|
||||
// })
|
||||
// }
|
||||
|
||||
// 登录方法
|
||||
export function login(username, password, code, uuid) {
|
||||
const grant_type = 'password'
|
||||
return request({
|
||||
url: '/auth/oauth/token',
|
||||
method: 'post',
|
||||
params: { username, password, code, uuid, client_id, client_secret, grant_type, scope }
|
||||
})
|
||||
}
|
||||
|
||||
// 刷新方法
|
||||
export function refreshToken(refresh_token) {
|
||||
const grant_type = 'refresh_token'
|
||||
return request({
|
||||
url: '/auth/oauth/token',
|
||||
method: 'post',
|
||||
params: { client_id, client_secret, grant_type, scope, refresh_token }
|
||||
})
|
||||
}
|
||||
|
||||
// 获取用户详细信息
|
||||
export function getInfo() {
|
||||
return request({
|
||||
url: '/system/user/getInfo',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 退出方法
|
||||
export function logout() {
|
||||
return request({
|
||||
url: '/auth/token/logout',
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取验证码
|
||||
export function getCodeImg() {
|
||||
return request({
|
||||
url: '/code',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
@ -1,115 +1,156 @@
|
||||
import { login, logout, getInfo, refreshToken } from '@/api/login'
|
||||
import { getToken, setToken, setExpiresIn, removeToken } from '@/utils/auth'
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
token: getToken(),
|
||||
name: '',
|
||||
avatar: '',
|
||||
roles: [],
|
||||
permissions: []
|
||||
},
|
||||
|
||||
mutations: {
|
||||
SET_TOKEN: (state, token) => {
|
||||
state.token = token
|
||||
},
|
||||
SET_EXPIRES_IN: (state, time) => {
|
||||
state.expires_in = time
|
||||
},
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
state.avatar = avatar
|
||||
},
|
||||
SET_ROLES: (state, roles) => {
|
||||
state.roles = roles
|
||||
},
|
||||
SET_PERMISSIONS: (state, permissions) => {
|
||||
state.permissions = permissions
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 登录
|
||||
Login({ commit }, userInfo) {
|
||||
const username = userInfo.username.trim()
|
||||
const password = userInfo.password
|
||||
const code = userInfo.code
|
||||
const uuid = userInfo.uuid
|
||||
return new Promise((resolve, reject) => {
|
||||
login(username, password, code, uuid).then(res => {
|
||||
let data = res.data
|
||||
setToken(data.access_token)
|
||||
commit('SET_TOKEN', data.access_token)
|
||||
setExpiresIn(data.expires_in)
|
||||
commit('SET_EXPIRES_IN', data.expires_in)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
GetInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo(state.token).then(res => {
|
||||
const user = res.user
|
||||
const avatar = user.avatar == "" ? require("@/assets/images/profile.jpg") : user.avatar;
|
||||
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
|
||||
commit('SET_ROLES', res.roles)
|
||||
commit('SET_PERMISSIONS', res.permissions)
|
||||
} else {
|
||||
commit('SET_ROLES', ['ROLE_DEFAULT'])
|
||||
}
|
||||
commit('SET_NAME', user.userName)
|
||||
commit('SET_AVATAR', avatar)
|
||||
resolve(res)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 刷新token
|
||||
RefreshToken({commit, state}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
refreshToken(state.token).then(res => {
|
||||
setExpiresIn(res.data)
|
||||
commit('SET_EXPIRES_IN', res.data)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 退出系统
|
||||
LogOut({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(state.token).then(() => {
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_ROLES', [])
|
||||
commit('SET_PERMISSIONS', [])
|
||||
removeToken()
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 前端 登出
|
||||
FedLogOut({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
commit('SET_TOKEN', '')
|
||||
removeToken()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default user
|
||||
import { login, logout, getInfo, refreshToken } from '@/api/login'
|
||||
import { getToken, setToken,getRefreshToken, setRefreshToken, setExpiresIn, removeToken } from '@/utils/auth'
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
token: getToken(),
|
||||
refresh_token: getRefreshToken(),
|
||||
name: '',
|
||||
avatar: '',
|
||||
roles: [],
|
||||
permissions: []
|
||||
},
|
||||
|
||||
mutations: {
|
||||
SET_TOKEN: (state, token) => {
|
||||
state.token = token
|
||||
},
|
||||
SET_EXPIRES_IN: (state, time) => {
|
||||
state.expires_in = time
|
||||
},
|
||||
SET_REFRESH_TOKEN: (state, token) => {
|
||||
state.refresh_token = token
|
||||
},
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
state.avatar = avatar
|
||||
},
|
||||
SET_ROLES: (state, roles) => {
|
||||
state.roles = roles
|
||||
},
|
||||
SET_PERMISSIONS: (state, permissions) => {
|
||||
state.permissions = permissions
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// // 登录
|
||||
// Login({ commit }, userInfo) {
|
||||
// const username = userInfo.username.trim()
|
||||
// const password = userInfo.password
|
||||
// const code = userInfo.code
|
||||
// const uuid = userInfo.uuid
|
||||
// return new Promise((resolve, reject) => {
|
||||
// login(username, password, code, uuid).then(res => {
|
||||
// let data = res.data
|
||||
// setToken(data.access_token)
|
||||
// commit('SET_TOKEN', data.access_token)
|
||||
// setExpiresIn(data.expires_in)
|
||||
// commit('SET_EXPIRES_IN', data.expires_in)
|
||||
// resolve()
|
||||
// }).catch(error => {
|
||||
// reject(error)
|
||||
// })
|
||||
// })
|
||||
// },
|
||||
// 登录
|
||||
Login({ commit }, userInfo) {
|
||||
const username = userInfo.username.trim()
|
||||
const password = userInfo.password
|
||||
const code = userInfo.code
|
||||
const uuid = userInfo.uuid
|
||||
return new Promise((resolve, reject) => {
|
||||
login(username, password, code, uuid).then(res => {
|
||||
setToken(res.access_token)
|
||||
commit('SET_TOKEN', res.access_token)
|
||||
setRefreshToken(res.refresh_token)
|
||||
commit('SET_REFRESH_TOKEN', res.refresh_token)
|
||||
setExpiresIn(res.expires_in)
|
||||
commit('SET_EXPIRES_IN', res.expires_in)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
GetInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo(state.token).then(res => {
|
||||
const user = res.user
|
||||
const avatar = user.avatar == "" ? require("@/assets/images/profile.jpg") : user.avatar;
|
||||
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
|
||||
commit('SET_ROLES', res.roles)
|
||||
commit('SET_PERMISSIONS', res.permissions)
|
||||
} else {
|
||||
commit('SET_ROLES', ['ROLE_DEFAULT'])
|
||||
}
|
||||
commit('SET_NAME', user.userName)
|
||||
commit('SET_AVATAR', avatar)
|
||||
resolve(res)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// // 刷新token
|
||||
// RefreshToken({commit, state}) {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// refreshToken(state.token).then(res => {
|
||||
// setExpiresIn(res.data)
|
||||
// commit('SET_EXPIRES_IN', res.data)
|
||||
// resolve()
|
||||
// }).catch(error => {
|
||||
// reject(error)
|
||||
// })
|
||||
// })
|
||||
// },
|
||||
|
||||
// 刷新token
|
||||
RefreshToken({commit, state}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
refreshToken(state.refresh_token).then(res => {
|
||||
setToken(res.access_token)
|
||||
commit('SET_TOKEN', res.access_token)
|
||||
setRefreshToken(res.refresh_token)
|
||||
commit('SET_REFRESH_TOKEN', res.refresh_token)
|
||||
setExpiresIn(res.expires_in)
|
||||
commit('SET_EXPIRES_IN', res.expires_in)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 退出系统
|
||||
LogOut({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(state.token).then(() => {
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_ROLES', [])
|
||||
commit('SET_PERMISSIONS', [])
|
||||
removeToken()
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 前端 登出
|
||||
FedLogOut({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
commit('SET_TOKEN', '')
|
||||
removeToken()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default user
|
||||
|
@ -1,29 +1,41 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const TokenKey = 'Admin-Token'
|
||||
|
||||
const ExpiresInKey = 'Admin-Expires-In'
|
||||
|
||||
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 getExpiresIn() {
|
||||
return Cookies.get(ExpiresInKey) || -1
|
||||
}
|
||||
|
||||
export function setExpiresIn(time) {
|
||||
return Cookies.set(ExpiresInKey, time)
|
||||
}
|
||||
|
||||
export function removeExpiresIn() {
|
||||
return Cookies.remove(ExpiresInKey)
|
||||
}
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const TokenKey = 'Admin-Token'
|
||||
const RefreshTokenKey = 'Admin-Refresh-Token'
|
||||
const ExpiresInKey = 'Admin-Expires-In'
|
||||
|
||||
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 getRefreshToken() {
|
||||
return Cookies.get(RefreshTokenKey) || ``
|
||||
}
|
||||
|
||||
export function setRefreshToken(token) {
|
||||
return Cookies.set(RefreshTokenKey, token)
|
||||
}
|
||||
|
||||
export function removeRefreshToken() {
|
||||
return Cookies.remove(RefreshTokenKey)
|
||||
}
|
||||
|
||||
export function getExpiresIn() {
|
||||
return Cookies.get(ExpiresInKey) || -1
|
||||
}
|
||||
|
||||
export function setExpiresIn(time) {
|
||||
return Cookies.set(ExpiresInKey, time)
|
||||
}
|
||||
|
||||
export function removeExpiresIn() {
|
||||
return Cookies.remove(ExpiresInKey)
|
||||
}
|
||||
|
@ -1,203 +1,204 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<h3 class="title">若依后台管理系统</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
|
||||
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
auto-complete="off"
|
||||
placeholder="密码"
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<el-input
|
||||
v-model="loginForm.code"
|
||||
auto-complete="off"
|
||||
placeholder="验证码"
|
||||
style="width: 63%"
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
|
||||
<el-form-item style="width:100%;">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="medium"
|
||||
type="primary"
|
||||
style="width:100%;"
|
||||
@click.native.prevent="handleLogin"
|
||||
>
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 底部 -->
|
||||
<div class="el-login-footer">
|
||||
<span>Copyright © 2018-2021 ruoyi.vip All Rights Reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCodeImg } from "@/api/login";
|
||||
import Cookies from "js-cookie";
|
||||
import { encrypt, decrypt } from '@/utils/jsencrypt'
|
||||
|
||||
export default {
|
||||
name: "Login",
|
||||
data() {
|
||||
return {
|
||||
codeUrl: "",
|
||||
cookiePassword: "",
|
||||
loginForm: {
|
||||
username: "admin",
|
||||
password: "admin123",
|
||||
rememberMe: false,
|
||||
code: "",
|
||||
uuid: ""
|
||||
},
|
||||
loginRules: {
|
||||
username: [
|
||||
{ required: true, trigger: "blur", message: "用户名不能为空" }
|
||||
],
|
||||
password: [
|
||||
{ required: true, trigger: "blur", message: "密码不能为空" }
|
||||
],
|
||||
code: [{ required: true, trigger: "change", message: "验证码不能为空" }]
|
||||
},
|
||||
loading: false,
|
||||
redirect: undefined
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler: function(route) {
|
||||
this.redirect = route.query && route.query.redirect;
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getCode();
|
||||
this.getCookie();
|
||||
},
|
||||
methods: {
|
||||
getCode() {
|
||||
getCodeImg().then(res => {
|
||||
this.codeUrl = "data:image/gif;base64," + res.img;
|
||||
this.loginForm.uuid = res.uuid;
|
||||
});
|
||||
},
|
||||
getCookie() {
|
||||
const username = Cookies.get("username");
|
||||
const password = Cookies.get("password");
|
||||
const rememberMe = Cookies.get('rememberMe')
|
||||
this.loginForm = {
|
||||
username: username === undefined ? this.loginForm.username : username,
|
||||
password: password === undefined ? this.loginForm.password : decrypt(password),
|
||||
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
|
||||
};
|
||||
},
|
||||
handleLogin() {
|
||||
this.$refs.loginForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
if (this.loginForm.rememberMe) {
|
||||
Cookies.set("username", this.loginForm.username, { expires: 30 });
|
||||
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
|
||||
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
|
||||
} else {
|
||||
Cookies.remove("username");
|
||||
Cookies.remove("password");
|
||||
Cookies.remove('rememberMe');
|
||||
}
|
||||
this.$store.dispatch("Login", this.loginForm).then(() => {
|
||||
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
|
||||
}).catch(() => {
|
||||
this.loading = false;
|
||||
this.getCode();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss">
|
||||
.login {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-image: url("../assets/images/login-background.jpg");
|
||||
background-size: cover;
|
||||
}
|
||||
.title {
|
||||
margin: 0px auto 30px auto;
|
||||
text-align: center;
|
||||
color: #707070;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
width: 400px;
|
||||
padding: 25px 25px 5px 25px;
|
||||
.el-input {
|
||||
height: 38px;
|
||||
input {
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
.input-icon {
|
||||
height: 39px;
|
||||
width: 14px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
.login-tip {
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
.login-code {
|
||||
width: 33%;
|
||||
height: 38px;
|
||||
float: right;
|
||||
img {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
.el-login-footer {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-family: Arial;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.login-code-img {
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<h3 class="title">若依后台管理系统</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
|
||||
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
auto-complete="off"
|
||||
placeholder="密码"
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<el-input
|
||||
v-model="loginForm.code"
|
||||
auto-complete="off"
|
||||
placeholder="验证码"
|
||||
style="width: 63%"
|
||||
@keyup.enter.native="handleLogin"
|
||||
>
|
||||
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
|
||||
<el-form-item style="width:100%;">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="medium"
|
||||
type="primary"
|
||||
style="width:100%;"
|
||||
@click.native.prevent="handleLogin"
|
||||
>
|
||||
<span v-if="!loading">登 录</span>
|
||||
<span v-else>登 录 中...</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 底部 -->
|
||||
<div class="el-login-footer">
|
||||
<span>Copyright © 2018-2021 ruoyi.vip All Rights Reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCodeImg } from "@/api/login";
|
||||
import Cookies from "js-cookie";
|
||||
import { encrypt, decrypt } from '@/utils/jsencrypt'
|
||||
|
||||
export default {
|
||||
name: "Login",
|
||||
data() {
|
||||
return {
|
||||
codeUrl: "",
|
||||
cookiePassword: "",
|
||||
loginForm: {
|
||||
username: "admin",
|
||||
password: "admin123",
|
||||
rememberMe: false,
|
||||
code: "",
|
||||
uuid: ""
|
||||
},
|
||||
loginRules: {
|
||||
username: [
|
||||
{ required: true, trigger: "blur", message: "用户名不能为空" }
|
||||
],
|
||||
password: [
|
||||
{ required: true, trigger: "blur", message: "密码不能为空" }
|
||||
],
|
||||
code: [{ required: true, trigger: "change", message: "验证码不能为空" }]
|
||||
},
|
||||
loading: false,
|
||||
redirect: undefined
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler: function(route) {
|
||||
this.redirect = route.query && route.query.redirect;
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getCode();
|
||||
this.getCookie();
|
||||
},
|
||||
methods: {
|
||||
getCode() {
|
||||
getCodeImg().then(res => {
|
||||
this.codeUrl = "data:image/gif;base64," + res.img;
|
||||
this.loginForm.uuid = res.uuid;
|
||||
});
|
||||
},
|
||||
getCookie() {
|
||||
const username = Cookies.get("username");
|
||||
const password = Cookies.get("password");
|
||||
const rememberMe = Cookies.get('rememberMe')
|
||||
this.loginForm = {
|
||||
username: username === undefined ? this.loginForm.username : username,
|
||||
password: password === undefined ? this.loginForm.password : decrypt(password),
|
||||
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
|
||||
};
|
||||
},
|
||||
handleLogin() {
|
||||
this.$refs.loginForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
if (this.loginForm.rememberMe) {
|
||||
Cookies.set("username", this.loginForm.username, { expires: 30 });
|
||||
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
|
||||
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
|
||||
} else {
|
||||
Cookies.remove("username");
|
||||
Cookies.remove("password");
|
||||
Cookies.remove('rememberMe');
|
||||
}
|
||||
this.$store.dispatch("Login", this.loginForm).then(() => {
|
||||
this.$router.push({ path: this.redirect || "/" }).catch((e)=>{console.log(e);});
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
this.loading = false;
|
||||
this.getCode();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss">
|
||||
.login {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-image: url("../assets/images/login-background.jpg");
|
||||
background-size: cover;
|
||||
}
|
||||
.title {
|
||||
margin: 0px auto 30px auto;
|
||||
text-align: center;
|
||||
color: #707070;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
width: 400px;
|
||||
padding: 25px 25px 5px 25px;
|
||||
.el-input {
|
||||
height: 38px;
|
||||
input {
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
.input-icon {
|
||||
height: 39px;
|
||||
width: 14px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
.login-tip {
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
.login-code {
|
||||
width: 33%;
|
||||
height: 38px;
|
||||
float: right;
|
||||
img {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
.el-login-footer {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-family: Arial;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.login-code-img {
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in new issue