mirror of https://github.com/jeecgboot/jeecg-boot
commit
f04f7f9abf
|
@ -117,8 +117,7 @@ public class JwtUtil {
|
||||||
public static String getUsername(String token) {
|
public static String getUsername(String token) {
|
||||||
try {
|
try {
|
||||||
DecodedJWT jwt = JWT.decode(token);
|
DecodedJWT jwt = JWT.decode(token);
|
||||||
LoginUser loginUser = JSONObject.parseObject(jwt.getClaim("sub").asString(), LoginUser.class);
|
return jwt.getClaim("username").asString();
|
||||||
return loginUser.getUsername();
|
|
||||||
} catch (JWTDecodeException e) {
|
} catch (JWTDecodeException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.jeecg.common.util;
|
package org.jeecg.common.util;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.jeecg.common.api.CommonAPI;
|
import org.jeecg.common.api.CommonAPI;
|
||||||
|
@ -11,14 +12,6 @@ import org.jeecg.common.exception.JeecgBoot401Exception;
|
||||||
import org.jeecg.common.system.util.JwtUtil;
|
import org.jeecg.common.system.util.JwtUtil;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import org.jeecg.config.security.JeecgRedisOAuth2AuthorizationService;
|
|
||||||
import org.springframework.data.redis.serializer.SerializationException;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author scott
|
* @Author scott
|
||||||
* @Date 2019/9/23 14:12
|
* @Date 2019/9/23 14:12
|
||||||
|
@ -122,7 +115,7 @@ public class TokenUtils {
|
||||||
throw new JeecgBoot401Exception("账号已被锁定,请联系管理员!");
|
throw new JeecgBoot401Exception("账号已被锁定,请联系管理员!");
|
||||||
}
|
}
|
||||||
// 校验token是否超时失效 & 或者账号密码是否错误
|
// 校验token是否超时失效 & 或者账号密码是否错误
|
||||||
if (!jwtTokenRefresh(token, username, user.getPassword())) {
|
if (!jwtTokenRefresh(token, username, user.getPassword(), redisUtil)) {
|
||||||
throw new JeecgBoot401Exception(CommonConstant.TOKEN_IS_INVALID_MSG);
|
throw new JeecgBoot401Exception(CommonConstant.TOKEN_IS_INVALID_MSG);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -151,15 +144,6 @@ public class TokenUtils {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean jwtTokenRefresh(String token, String userName, String passWord) {
|
|
||||||
JeecgRedisOAuth2AuthorizationService authRedis = SpringContextUtils.getBean(JeecgRedisOAuth2AuthorizationService.class);
|
|
||||||
OAuth2Authorization authorization = authRedis.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
|
|
||||||
if (Objects.nonNull(authorization) && JwtUtil.verify(token, userName, passWord)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取登录用户
|
* 获取登录用户
|
||||||
*
|
*
|
||||||
|
|
|
@ -25,12 +25,18 @@ public class CopyTokenFilter extends OncePerRequestFilter {
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
// 以下为undertow定制代码,如切换其它servlet容器,需要同步更换
|
// 以下为undertow定制代码,如切换其它servlet容器,需要同步更换
|
||||||
HttpServletRequestImpl undertowRequest = (HttpServletRequestImpl) request;
|
HttpServletRequestImpl undertowRequest = (HttpServletRequestImpl) request;
|
||||||
String bearerToken = request.getParameter("token");
|
String token = request.getHeader("Authorization");
|
||||||
String headerBearerToken = request.getHeader("X-Access-Token");
|
if (StringUtils.hasText(token)) {
|
||||||
if (StringUtils.hasText(bearerToken)) {
|
undertowRequest.getExchange().getRequestHeaders().remove("Authorization");
|
||||||
undertowRequest.getExchange().getRequestHeaders().add(new HttpString("Authorization"), "bearer " + bearerToken);
|
undertowRequest.getExchange().getRequestHeaders().add(new HttpString("Authorization"), "bearer " + token);
|
||||||
} else if (StringUtils.hasText(headerBearerToken)) {
|
} else {
|
||||||
undertowRequest.getExchange().getRequestHeaders().add(new HttpString("Authorization"), "bearer " + headerBearerToken);
|
String bearerToken = request.getParameter("token");
|
||||||
|
String headerBearerToken = request.getHeader("X-Access-Token");
|
||||||
|
if (StringUtils.hasText(bearerToken)) {
|
||||||
|
undertowRequest.getExchange().getRequestHeaders().add(new HttpString("Authorization"), "bearer " + bearerToken);
|
||||||
|
} else if (StringUtils.hasText(headerBearerToken)) {
|
||||||
|
undertowRequest.getExchange().getRequestHeaders().add(new HttpString("Authorization"), "bearer " + headerBearerToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
filterChain.doFilter(undertowRequest, response);
|
filterChain.doFilter(undertowRequest, response);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.jeecg.config.security;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jeecg.common.api.CommonAPI;
|
||||||
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token只存储用户名与过期时间
|
||||||
|
* 这里通过取用户名转全量用户信息存储到Security中
|
||||||
|
* @author eightmonth@qq.com
|
||||||
|
* @date 2024/7/15 11:05
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JeecgAuthenticationConvert implements Converter<Jwt, AbstractAuthenticationToken> {
|
||||||
|
|
||||||
|
private final CommonAPI commonAPI;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AbstractAuthenticationToken convert(Jwt source) {
|
||||||
|
String username = source.getClaims().get("username").toString();
|
||||||
|
LoginUser loginUser = commonAPI.getUserByName(username);
|
||||||
|
return new UsernamePasswordAuthenticationToken(loginUser, null, new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package org.jeecg.config.security;
|
||||||
|
|
||||||
|
import org.jeecg.common.system.util.JwtUtil;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwsHeader;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.*;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.Temporal;
|
||||||
|
import java.time.temporal.TemporalUnit;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author eightmonth@qq.com
|
||||||
|
* @date 2024/7/11 17:10
|
||||||
|
*/
|
||||||
|
public class JeecgOAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OAuth2AccessToken> {
|
||||||
|
private final JwtEncoder jwtEncoder;
|
||||||
|
|
||||||
|
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
|
||||||
|
|
||||||
|
public JeecgOAuth2AccessTokenGenerator(JwtEncoder jwtEncoder) {
|
||||||
|
this.jwtEncoder = jwtEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public OAuth2AccessToken generate(OAuth2TokenContext context) {
|
||||||
|
if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String issuer = null;
|
||||||
|
if (context.getAuthorizationServerContext() != null) {
|
||||||
|
issuer = context.getAuthorizationServerContext().getIssuer();
|
||||||
|
}
|
||||||
|
RegisteredClient registeredClient = context.getRegisteredClient();
|
||||||
|
|
||||||
|
Instant issuedAt = Instant.now();
|
||||||
|
Instant expiresAt = issuedAt.plusMillis(JwtUtil.EXPIRE_TIME);
|
||||||
|
|
||||||
|
OAuth2TokenClaimsSet.Builder claimsBuilder = OAuth2TokenClaimsSet.builder();
|
||||||
|
if (StringUtils.hasText(issuer)) {
|
||||||
|
claimsBuilder.issuer(issuer);
|
||||||
|
}
|
||||||
|
claimsBuilder
|
||||||
|
.subject(context.getPrincipal().getName())
|
||||||
|
.audience(Collections.singletonList(registeredClient.getClientId()))
|
||||||
|
.issuedAt(issuedAt)
|
||||||
|
.expiresAt(expiresAt)
|
||||||
|
.notBefore(issuedAt)
|
||||||
|
.id(UUID.randomUUID().toString());
|
||||||
|
if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) {
|
||||||
|
claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.accessTokenCustomizer != null) {
|
||||||
|
OAuth2TokenClaimsContext.Builder accessTokenContextBuilder = OAuth2TokenClaimsContext.with(claimsBuilder)
|
||||||
|
.registeredClient(context.getRegisteredClient())
|
||||||
|
.principal(context.getPrincipal())
|
||||||
|
.authorizationServerContext(context.getAuthorizationServerContext())
|
||||||
|
.authorizedScopes(context.getAuthorizedScopes())
|
||||||
|
.tokenType(context.getTokenType())
|
||||||
|
.authorizationGrantType(context.getAuthorizationGrantType());
|
||||||
|
if (context.getAuthorization() != null) {
|
||||||
|
accessTokenContextBuilder.authorization(context.getAuthorization());
|
||||||
|
}
|
||||||
|
if (context.getAuthorizationGrant() != null) {
|
||||||
|
accessTokenContextBuilder.authorizationGrant(context.getAuthorizationGrant());
|
||||||
|
}
|
||||||
|
|
||||||
|
OAuth2TokenClaimsContext accessTokenContext = accessTokenContextBuilder.build();
|
||||||
|
this.accessTokenCustomizer.customize(accessTokenContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build();
|
||||||
|
OAuth2AuthorizationGrantAuthenticationToken oAuth2ResourceOwnerBaseAuthenticationToken = context.getAuthorizationGrant();
|
||||||
|
String username = (String) oAuth2ResourceOwnerBaseAuthenticationToken.getAdditionalParameters().get("username");
|
||||||
|
String tokenValue = jwtEncoder.encode(JwtEncoderParameters.from(JwsHeader.with(SignatureAlgorithm.ES256).keyId("jeecg").build(),
|
||||||
|
JwtClaimsSet.builder().claim("username", username).expiresAt(expiresAt).build())).getTokenValue();
|
||||||
|
|
||||||
|
//此处可以做改造将tokenValue随机数换成用户信息,方便后续多系统token互通认证(通过解密token得到username)
|
||||||
|
return new OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER, tokenValue,
|
||||||
|
accessTokenClaimsSet.getIssuedAt(), accessTokenClaimsSet.getExpiresAt(), context.getAuthorizedScopes(),
|
||||||
|
accessTokenClaimsSet.getClaims());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OAuth2TokenCustomizer} that customizes the
|
||||||
|
* {@link OAuth2TokenClaimsContext#getClaims() claims} for the
|
||||||
|
* {@link OAuth2AccessToken}.
|
||||||
|
* @param accessTokenCustomizer the {@link OAuth2TokenCustomizer} that customizes the
|
||||||
|
* claims for the {@code OAuth2AccessToken}
|
||||||
|
*/
|
||||||
|
public void setAccessTokenCustomizer(OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer) {
|
||||||
|
Assert.notNull(accessTokenCustomizer, "accessTokenCustomizer cannot be null");
|
||||||
|
this.accessTokenCustomizer = accessTokenCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor {
|
||||||
|
|
||||||
|
private final Map<String, Object> claims;
|
||||||
|
|
||||||
|
private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt,
|
||||||
|
Set<String> scopes, Map<String, Object> claims) {
|
||||||
|
super(tokenType, tokenValue, issuedAt, expiresAt, scopes);
|
||||||
|
this.claims = claims;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getClaims() {
|
||||||
|
return this.claims;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
package org.jeecg.config.security;
|
package org.jeecg.config.security;
|
||||||
|
|
||||||
|
import com.nimbusds.jose.jwk.Curve;
|
||||||
|
import com.nimbusds.jose.jwk.ECKey;
|
||||||
import com.nimbusds.jose.jwk.JWKSet;
|
import com.nimbusds.jose.jwk.JWKSet;
|
||||||
import com.nimbusds.jose.jwk.RSAKey;
|
|
||||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||||
import com.nimbusds.jose.proc.SecurityContext;
|
import com.nimbusds.jose.proc.SecurityContext;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import org.jeecg.config.security.app.AppGrantAuthenticationConvert;
|
import org.jeecg.config.security.app.AppGrantAuthenticationConvert;
|
||||||
import org.jeecg.config.security.app.AppGrantAuthenticationProvider;
|
import org.jeecg.config.security.app.AppGrantAuthenticationProvider;
|
||||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationConvert;
|
import org.jeecg.config.security.password.PasswordGrantAuthenticationConvert;
|
||||||
|
@ -38,8 +40,6 @@ import org.springframework.security.oauth2.server.authorization.settings.Authori
|
||||||
import org.springframework.security.oauth2.server.authorization.token.*;
|
import org.springframework.security.oauth2.server.authorization.token.*;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.header.writers.frameoptions.RegExpAllowFromStrategy;
|
|
||||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
|
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
@ -47,10 +47,9 @@ import org.springframework.web.cors.CorsConfiguration;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
import java.security.interfaces.ECPrivateKey;
|
||||||
import java.security.interfaces.RSAPublicKey;
|
import java.security.interfaces.ECPublicKey;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* spring authorization server核心配置
|
* spring authorization server核心配置
|
||||||
|
@ -65,6 +64,7 @@ public class SecurityConfig {
|
||||||
|
|
||||||
private JdbcTemplate jdbcTemplate;
|
private JdbcTemplate jdbcTemplate;
|
||||||
private OAuth2AuthorizationService authorizationService;
|
private OAuth2AuthorizationService authorizationService;
|
||||||
|
private JeecgAuthenticationConvert jeecgAuthenticationConvert;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Order(1)
|
@Order(1)
|
||||||
|
@ -90,10 +90,7 @@ public class SecurityConfig {
|
||||||
new LoginUrlAuthenticationEntryPoint("/sys/login"),
|
new LoginUrlAuthenticationEntryPoint("/sys/login"),
|
||||||
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
|
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
// 使用jwt处理接收到的access token
|
|
||||||
.oauth2ResourceServer(oauth2ResourceServer ->
|
|
||||||
oauth2ResourceServer.jwt(Customizer.withDefaults()));
|
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
@ -176,7 +173,7 @@ public class SecurityConfig {
|
||||||
return config;
|
return config;
|
||||||
}))
|
}))
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
|
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jeecgAuthenticationConvert)));
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,16 +190,23 @@ public class SecurityConfig {
|
||||||
* JWK详细见:https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key-41
|
* JWK详细见:https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key-41
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
|
@SneakyThrows
|
||||||
public JWKSource<SecurityContext> jwkSource() {
|
public JWKSource<SecurityContext> jwkSource() {
|
||||||
KeyPair keyPair = generateRsaKey();
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
|
||||||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
|
// 如果不设置secureRandom,会存在一个问题,当应用重启后,原有的token将会全部失效,因为重启的keyPair与之前已经不同
|
||||||
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
|
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
|
||||||
RSAKey rsaKey = new RSAKey.Builder(publicKey)
|
// 重要!生产环境需要修改!
|
||||||
|
secureRandom.setSeed("jeecg".getBytes());
|
||||||
|
keyPairGenerator.initialize(256, secureRandom);
|
||||||
|
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||||
|
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
|
||||||
|
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
|
||||||
|
|
||||||
|
ECKey jwk = new ECKey.Builder(Curve.P_256, publicKey)
|
||||||
.privateKey(privateKey)
|
.privateKey(privateKey)
|
||||||
// 重要!生产环境需要修改!
|
|
||||||
.keyID("jeecg")
|
.keyID("jeecg")
|
||||||
.build();
|
.build();
|
||||||
JWKSet jwkSet = new JWKSet(rsaKey);
|
JWKSet jwkSet = new JWKSet(jwk);
|
||||||
return new ImmutableJWKSet<>(jwkSet);
|
return new ImmutableJWKSet<>(jwkSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,28 +215,6 @@ public class SecurityConfig {
|
||||||
return NoOpPasswordEncoder.getInstance();
|
return NoOpPasswordEncoder.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*生成RSA密钥对,给上面jwkSource() 方法的提供密钥对
|
|
||||||
*/
|
|
||||||
private static KeyPair generateRsaKey() {
|
|
||||||
KeyPair keyPair;
|
|
||||||
try {
|
|
||||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
|
||||||
|
|
||||||
// 生产环境不应该设置secureRandom,seed如果被泄露,jwt容易被伪造
|
|
||||||
// 如果不设置secureRandom,会存在一个问题,当应用重启后,原有的token将会全部失效,因为重启的keyPair与之前已经不同
|
|
||||||
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
|
|
||||||
// 重要!生产环境需要修改!
|
|
||||||
secureRandom.setSeed("jeecg".getBytes());
|
|
||||||
keyPairGenerator.initialize(2048, secureRandom);
|
|
||||||
keyPair = keyPairGenerator.generateKeyPair();
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
throw new IllegalStateException(ex);
|
|
||||||
}
|
|
||||||
return keyPair;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置jwt解析器
|
* 配置jwt解析器
|
||||||
*/
|
*/
|
||||||
|
@ -241,14 +223,6 @@ public class SecurityConfig {
|
||||||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*配置认证服务器请求地址
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public AuthorizationServerSettings authorizationServerSettings() {
|
|
||||||
return AuthorizationServerSettings.builder().tokenEndpoint("/sys/login").build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*配置token生成器
|
*配置token生成器
|
||||||
*/
|
*/
|
||||||
|
@ -258,7 +232,9 @@ public class SecurityConfig {
|
||||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
||||||
return new DelegatingOAuth2TokenGenerator(
|
return new DelegatingOAuth2TokenGenerator(
|
||||||
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
|
new JeecgOAuth2AccessTokenGenerator(new NimbusJwtEncoder(jwkSource())),
|
||||||
|
new OAuth2RefreshTokenGenerator()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.jeecg.config.security.self;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import org.jeecg.common.api.CommonAPI;
|
import org.jeecg.common.api.CommonAPI;
|
||||||
import org.jeecg.common.constant.CommonConstant;
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
import org.jeecg.common.exception.JeecgBoot401Exception;
|
||||||
import org.jeecg.common.exception.JeecgBootException;
|
import org.jeecg.common.exception.JeecgBootException;
|
||||||
import org.jeecg.common.system.vo.LoginUser;
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
import org.jeecg.common.system.vo.SysDepartModel;
|
import org.jeecg.common.system.vo.SysDepartModel;
|
||||||
|
@ -80,19 +81,13 @@ public class SelfAuthenticationProvider implements AuthenticationProvider {
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken);
|
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken);
|
||||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||||
|
|
||||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("message", "非法登录");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过用户名获取用户信息
|
// 通过用户名获取用户信息
|
||||||
LoginUser loginUser = commonAPI.getUserByName(username);
|
// LoginUser loginUser = commonAPI.getUserByName(username);
|
||||||
// 检查用户可行性
|
// 检查用户可行性
|
||||||
checkUserIsEffective(loginUser);
|
// checkUserIsEffective(loginUser);
|
||||||
|
|
||||||
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
||||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
|
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(username,clientPrincipal,new ArrayList<>());
|
||||||
|
|
||||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
||||||
.registeredClient(registeredClient)
|
.registeredClient(registeredClient)
|
||||||
|
@ -112,10 +107,7 @@ public class SelfAuthenticationProvider implements AuthenticationProvider {
|
||||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
||||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
||||||
if (generatedAccessToken == null) {
|
if (generatedAccessToken == null) {
|
||||||
Map<String, Object> map = new HashMap<>();
|
throw new JeecgBoot401Exception("无法生成刷新token,请联系管理员。");
|
||||||
map.put("message", "无法生成刷新token,请联系管理员。");
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,"fdsafas", Instant.now(), Instant.now().plusNanos(1)), null, map);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||||
|
@ -128,7 +120,7 @@ public class SelfAuthenticationProvider implements AuthenticationProvider {
|
||||||
authorizationBuilder.accessToken(accessToken);
|
authorizationBuilder.accessToken(accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- Refresh token -----
|
// ----- Refresh token -----
|
||||||
OAuth2RefreshToken refreshToken = null;
|
OAuth2RefreshToken refreshToken = null;
|
||||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
||||||
// 不向公共客户端颁发刷新令牌
|
// 不向公共客户端颁发刷新令牌
|
||||||
|
@ -151,40 +143,8 @@ public class SelfAuthenticationProvider implements AuthenticationProvider {
|
||||||
// 保存认证信息至redis
|
// 保存认证信息至redis
|
||||||
authorizationService.save(authorization);
|
authorizationService.save(authorization);
|
||||||
|
|
||||||
JSONObject addition = new JSONObject(new LinkedHashMap<>());
|
|
||||||
addition.put("token", accessToken.getTokenValue());
|
|
||||||
// 设置租户
|
|
||||||
JSONObject jsonObject = commonAPI.setLoginTenant(username);
|
|
||||||
addition.putAll(jsonObject.getInnerMap());
|
|
||||||
|
|
||||||
// 设置登录用户信息
|
|
||||||
addition.put("userInfo", loginUser);
|
|
||||||
addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
|
|
||||||
|
|
||||||
List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
|
|
||||||
addition.put("departs", departs);
|
|
||||||
if (departs == null || departs.size() == 0) {
|
|
||||||
addition.put("multi_depart", 0);
|
|
||||||
} else if (departs.size() == 1) {
|
|
||||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
|
||||||
addition.put("multi_depart", 1);
|
|
||||||
} else {
|
|
||||||
//查询当前是否有登录部门
|
|
||||||
if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
|
|
||||||
commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
|
|
||||||
}
|
|
||||||
addition.put("multi_depart", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 兼容原有shiro登录结果处理
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("result", addition);
|
|
||||||
map.put("code", 200);
|
|
||||||
map.put("success", true);
|
|
||||||
map.put("timestamp", System.currentTimeMillis());
|
|
||||||
|
|
||||||
// 返回access_token、refresh_token以及其它信息给到前端
|
// 返回access_token、refresh_token以及其它信息给到前端
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, map);
|
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -89,9 +89,8 @@ public class LoginController {
|
||||||
* @param sysLoginModel
|
* @param sysLoginModel
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Operation(summary = "登录接口")
|
||||||
// @Operation(summary = "登录接口")
|
@RequestMapping(value = "/login", method = RequestMethod.POST)
|
||||||
// @RequestMapping(value = "/login", method = RequestMethod.POST)
|
|
||||||
public Result<JSONObject> login(@RequestBody SysLoginModel sysLoginModel, HttpServletRequest request){
|
public Result<JSONObject> login(@RequestBody SysLoginModel sysLoginModel, HttpServletRequest request){
|
||||||
Result<JSONObject> result = new Result<JSONObject>();
|
Result<JSONObject> result = new Result<JSONObject>();
|
||||||
String username = sysLoginModel.getUsername();
|
String username = sysLoginModel.getUsername();
|
||||||
|
|
|
@ -16,6 +16,22 @@ JeecgBoot-Vue3采用 Vue3.0、Vite、 Ant-Design-Vue4、TypeScript 等新技术
|
||||||
|
|
||||||
> 强大的代码生成器让前后端代码一键生成! JeecgBoot引领低代码开发模式(OnlineCoding-> 代码生成-> 手工MERGE), 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省成本,同时又不失灵活性
|
> 强大的代码生成器让前后端代码一键生成! JeecgBoot引领低代码开发模式(OnlineCoding-> 代码生成-> 手工MERGE), 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省成本,同时又不失灵活性
|
||||||
|
|
||||||
|
## 技术支持
|
||||||
|
|
||||||
|
关闭Gitee的issue通道,使用中遇到问题或者BUG可以在 [Github上提Issues](https://github.com/jeecgboot/jeecgboot-vue3/issues/new)
|
||||||
|
|
||||||
|
##### 源码下载
|
||||||
|
|
||||||
|
- JAVA后台源码:https://github.com/jeecgboot/JeecgBoot
|
||||||
|
|
||||||
|
|
||||||
|
##### 项目说明
|
||||||
|
|
||||||
|
| 项目名 | 说明 |
|
||||||
|
|--------------------|-----------------------------------------|
|
||||||
|
| `jeecgboot-vue3` | 前端源码Vue3 |
|
||||||
|
| `jeecg-boot` | 后端源码JAVA(SpringBoot+SpringCloud) |
|
||||||
|
|
||||||
|
|
||||||
## 开发环境搭建
|
## 开发环境搭建
|
||||||
|
|
||||||
|
@ -28,7 +44,7 @@ JeecgBoot-Vue3采用 Vue3.0、Vite、 Ant-Design-Vue4、TypeScript 等新技术
|
||||||
- 官方文档:[https://help.jeecg.com](https://help.jeecg.com)
|
- 官方文档:[https://help.jeecg.com](https://help.jeecg.com)
|
||||||
- 快速入门:[快速入门](http://jeecg.com/doc/quickstart) | [常见问题](http://help.jeecg.com/qa.html) | [视频教程](https://www.bilibili.com/video/BV1V34y187Y9 "入门视频") | [ 代码生成](http://help.jeecg.com/vue3/codegen/online.html)
|
- 快速入门:[快速入门](http://jeecg.com/doc/quickstart) | [常见问题](http://help.jeecg.com/qa.html) | [视频教程](https://www.bilibili.com/video/BV1V34y187Y9 "入门视频") | [ 代码生成](http://help.jeecg.com/vue3/codegen/online.html)
|
||||||
- QQ交流群:⑨808791225、⑧825232878(满)、⑦791696430(满)、683903138(满)、其他满
|
- QQ交流群:⑨808791225、⑧825232878(满)、⑦791696430(满)、683903138(满)、其他满
|
||||||
- 在线演示 : [系统演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex)
|
- 在线演示 : [Vue3演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex)
|
||||||
> 演示系统的登录账号密码,请点击 [获取账号密码](http://jeecg.com/doc/demo) 获取
|
> 演示系统的登录账号密码,请点击 [获取账号密码](http://jeecg.com/doc/demo) 获取
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,13 +58,13 @@ JeecgBoot-Vue3采用 Vue3.0、Vite、 Ant-Design-Vue4、TypeScript 等新技术
|
||||||
- Get the project code
|
- Get the project code
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/jeecgboot/JeecgBoot.git
|
git clone https://github.com/jeecgboot/jeecgboot-vue3.git
|
||||||
```
|
```
|
||||||
|
|
||||||
- Installation dependencies
|
- Installation dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd JeecgBoot/jeecgboot-vue3
|
cd jeecgboot-vue3
|
||||||
|
|
||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
@ -92,9 +108,9 @@ pnpm build
|
||||||
- 下载项目
|
- 下载项目
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/jeecgboot/JeecgBoot.git
|
git clone https://github.com/jeecgboot/jeecgboot-vue3.git
|
||||||
|
|
||||||
cd JeecgBoot/jeecgboot-vue3
|
cd jeecgboot-vue3
|
||||||
```
|
```
|
||||||
|
|
||||||
- 配置接口域名 `.env.production`
|
- 配置接口域名 `.env.production`
|
||||||
|
@ -150,11 +166,16 @@ VITE_GLOB_DOMAIN_URL=http://jeecg-boot-gateway:9999
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 入门必备
|
## 入门必备
|
||||||
|
|
||||||
本项目需要一定前端基础知识,请确保掌握 Vue 的基础知识,以便能处理一些常见的问题。 建议在开发前先学一下以下内容,提前了解和学习这些知识,会对项目理解非常有帮助:
|
本项目需要一定前端基础知识,请确保掌握 Vue 的基础知识,以便能处理一些常见的问题。 建议在开发前先学一下以下内容,提前了解和学习这些知识,会对项目理解非常有帮助:
|
||||||
|
|
||||||
* [JeecgBoot文档](http://help.jeecg.com)
|
* [JeecgBoot-Vue3文档](http://help.jeecg.com)
|
||||||
* [Vue3 文档](https://cn.vuejs.org/)
|
* [Vue3 文档](https://cn.vuejs.org/)
|
||||||
* [Vben文档](https://doc.vvbin.cn)
|
* [Vben文档](https://doc.vvbin.cn)
|
||||||
* [Ant-Design-Vue](https://www.antdv.com/docs/vue/introduce-cn/)
|
* [Ant-Design-Vue](https://www.antdv.com/docs/vue/introduce-cn/)
|
||||||
|
@ -178,3 +199,246 @@ VITE_GLOB_DOMAIN_URL=http://jeecg-boot-gateway:9999
|
||||||
| [](http://godban.github.io/browsers-support-badges/)IE | [](http://godban.github.io/browsers-support-badges/)Edge | [](http://godban.github.io/browsers-support-badges/)Firefox | [](http://godban.github.io/browsers-support-badges/)Chrome | [](http://godban.github.io/browsers-support-badges/)Safari |
|
| [](http://godban.github.io/browsers-support-badges/)IE | [](http://godban.github.io/browsers-support-badges/)Edge | [](http://godban.github.io/browsers-support-badges/)Firefox | [](http://godban.github.io/browsers-support-badges/)Chrome | [](http://godban.github.io/browsers-support-badges/)Safari |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 功能模块
|
||||||
|
> vue3版本已经实现了系统管理、系统监控、报表、各种组件、前端权限、GUI代码生成、Online表单、Online报表等平台功能,完全可以用于生产环境。
|
||||||
|
|
||||||
|
```
|
||||||
|
├─首页
|
||||||
|
│ ├─首页(四套首页满足不同场景需求)
|
||||||
|
│ ├─工作台
|
||||||
|
├─系统管理
|
||||||
|
│ ├─用户管理
|
||||||
|
│ ├─角色管理
|
||||||
|
│ ├─菜单管理
|
||||||
|
│ ├─权限设置(支持按钮权限、数据权限)
|
||||||
|
│ ├─表单权限(控制字段禁用、隐藏)
|
||||||
|
│ ├─部门管理
|
||||||
|
│ ├─我的部门(二级管理员)
|
||||||
|
│ └─字典管理
|
||||||
|
│ └─分类字典
|
||||||
|
│ └─系统公告
|
||||||
|
│ └─职务管理
|
||||||
|
│ └─通讯录
|
||||||
|
│ └─对象存储
|
||||||
|
│ └─多租户管理
|
||||||
|
├─系统监控
|
||||||
|
│ ├─网关路由配置(gateway)
|
||||||
|
│ ├─定时任务
|
||||||
|
│ ├─数据源管理
|
||||||
|
│ ├─系统日志
|
||||||
|
│ ├─消息中心(支持短信、邮件、微信推送等等)
|
||||||
|
│ ├─数据日志(记录数据快照,可对比快照,查看数据变更情况)
|
||||||
|
│ ├─系统通知
|
||||||
|
│ ├─SQL监控
|
||||||
|
│ ├─性能监控
|
||||||
|
│ │ ├─监控 Redis
|
||||||
|
│ │ ├─Tomcat
|
||||||
|
│ │ ├─jvm
|
||||||
|
│ │ ├─服务器信息
|
||||||
|
│ │ ├─请求追踪
|
||||||
|
│ │ ├─磁盘监控
|
||||||
|
├─消息中心
|
||||||
|
│ ├─我的消息
|
||||||
|
│ ├─消息管理
|
||||||
|
│ ├─模板管理
|
||||||
|
├─积木报表设计器
|
||||||
|
│─报表示例
|
||||||
|
│ ├─曲线图
|
||||||
|
│ └─饼状图
|
||||||
|
│ └─柱状图
|
||||||
|
│ └─折线图
|
||||||
|
│ └─面积图
|
||||||
|
│ └─雷达图
|
||||||
|
│ └─仪表图
|
||||||
|
│ └─进度条
|
||||||
|
│ └─排名列表
|
||||||
|
│ └─等等
|
||||||
|
│─大屏模板
|
||||||
|
│ ├─作战指挥中心大屏
|
||||||
|
│ └─物流服务中心大屏
|
||||||
|
├─代码生成器(GUI)
|
||||||
|
│ ├─代码生成器功能(一键生成前后端代码,生成后无需修改直接用,绝对是后端开发福音)
|
||||||
|
│ ├─代码生成器模板(提供4套模板,分别支持单表和一对多模型,不同风格选择)
|
||||||
|
│ ├─代码生成器模板(生成代码,自带excel导入导出)
|
||||||
|
│ ├─查询过滤器(查询逻辑无需编码,系统根据页面配置自动生成)
|
||||||
|
│ ├─高级查询器(弹窗自动组合查询条件)
|
||||||
|
│ ├─Excel导入导出工具集成(支持单表,一对多 导入导出)
|
||||||
|
│ ├─平台移动自适应支持
|
||||||
|
│─常用示例
|
||||||
|
│ ├─自定义组件示例
|
||||||
|
│ ├─JVxeTable示例(ERP行业复杂排版效果)
|
||||||
|
│ ├─单表模型例子
|
||||||
|
│ └─一对多模型例子
|
||||||
|
│ └─打印例子
|
||||||
|
│ └─一对多内嵌示例
|
||||||
|
│ └─异步树Table
|
||||||
|
│ └─图片拖拽排序
|
||||||
|
│ └─图片翻页
|
||||||
|
│ └─图片预览
|
||||||
|
│ └─PDF预览
|
||||||
|
│─封装通用组件
|
||||||
|
│ ├─行编辑表格JVxeTable
|
||||||
|
│ └─省略显示组件
|
||||||
|
│ └─时间控件
|
||||||
|
│ └─高级查询 (未实现)
|
||||||
|
│ └─用户选择组件
|
||||||
|
│ └─报表组件封装
|
||||||
|
│ └─字典组件
|
||||||
|
│ └─下拉多选组件
|
||||||
|
│ └─选人组件
|
||||||
|
│ └─选部门组件
|
||||||
|
│ └─通过部门选人组件
|
||||||
|
│ └─封装曲线、柱状图、饼状图、折线图等等报表的组件(经过封装,使用简单)
|
||||||
|
│ └─在线code编辑器
|
||||||
|
│ └─上传文件组件
|
||||||
|
│ └─树列表组件
|
||||||
|
│ └─表单禁用组件
|
||||||
|
│ └─等等
|
||||||
|
│─更多页面模板
|
||||||
|
│ └─Mock示例(子菜单很多)
|
||||||
|
│ └─页面&导航(子菜单很多)
|
||||||
|
│ └─组件&功能(子菜单很多)
|
||||||
|
├─高级功能
|
||||||
|
│ ├─支持微前端
|
||||||
|
│ ├─提供CAS单点登录
|
||||||
|
│ ├─集成Websocket消息通知机制
|
||||||
|
│ ├─支持第三方登录(QQ、钉钉、微信等)
|
||||||
|
│ ├─系统编码规则
|
||||||
|
├─Online在线开发(低代码)
|
||||||
|
│ ├─Online在线表单 - 功能已开放
|
||||||
|
│ ├─Online代码生成器 - 功能已开放
|
||||||
|
│ ├─Online在线报表 - 功能已开放
|
||||||
|
│ ├─Online在线图表(暂未开源)
|
||||||
|
│ ├─多数据源管理
|
||||||
|
│─流程模块功能 (暂未开源)
|
||||||
|
│ ├─流程设计器
|
||||||
|
│ ├─表单设计器
|
||||||
|
│ ├─大屏设计器
|
||||||
|
│ ├─门户设计/仪表盘设计器
|
||||||
|
│ └─我的任务
|
||||||
|
│ └─历史流程
|
||||||
|
│ └─历史流程
|
||||||
|
│ └─流程实例管理
|
||||||
|
│ └─流程监听管理
|
||||||
|
│ └─流程表达式
|
||||||
|
│ └─我发起的流程
|
||||||
|
│ └─我的抄送
|
||||||
|
│ └─流程委派、抄送、跳转
|
||||||
|
│ └─OA办公组件
|
||||||
|
└─其他模块
|
||||||
|
└─更多功能开发中。。
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 系统效果
|
||||||
|
系统后台
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Online开发&代码生成
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
系统交互
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
流程设计
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
简版流程设计
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
仪表盘设计器
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
报表设计器
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
表单设计器
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
大屏设计器
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
报表效果
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
接口文档
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
手机端
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
PAD端
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"author": {
|
"author": {
|
||||||
"name": "北京国炬信息技术有限公司",
|
"name": "北京国炬信息技术有限公司",
|
||||||
"email": "jeecgos@163.com",
|
"email": "jeecgos@163.com",
|
||||||
"url": "https://www.jeecg.com"
|
"url": "https://github.com/jeecgboot/jeecgboot-vue3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pinstall": "pnpm install",
|
"pinstall": "pnpm install",
|
||||||
|
@ -160,11 +160,11 @@
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/jeecgboot/JeecgBoot.git"
|
"url": "git+https://github.com/jeecgboot/jeecgboot-vue3.git"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/jeecgboot/JeecgBoot/issues"
|
"url": "https://github.com/jeecgboot/jeecgboot-vue3/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://www.jeecg.com",
|
"homepage": "https://www.jeecg.com",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,13 +4,11 @@
|
||||||
export interface LoginParams {
|
export interface LoginParams {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
grant_type: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ThirdLoginParams {
|
export interface ThirdLoginParams {
|
||||||
token: string;
|
token: string;
|
||||||
thirdType: string;
|
thirdType: string;
|
||||||
grant_type: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RoleInfo {
|
export interface RoleInfo {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { ExceptionEnum } from "@/enums/exceptionEnum";
|
||||||
const { createErrorModal } = useMessage();
|
const { createErrorModal } = useMessage();
|
||||||
enum Api {
|
enum Api {
|
||||||
Login = '/sys/login',
|
Login = '/sys/login',
|
||||||
phoneLogin = '/oauth2/token',
|
phoneLogin = '/sys/phoneLogin',
|
||||||
Logout = '/sys/logout',
|
Logout = '/sys/logout',
|
||||||
GetUserInfo = '/sys/user/getUserInfo',
|
GetUserInfo = '/sys/user/getUserInfo',
|
||||||
// 获取系统权限
|
// 获取系统权限
|
||||||
|
@ -36,7 +36,7 @@ enum Api {
|
||||||
//修改密码
|
//修改密码
|
||||||
passwordChange = '/sys/user/passwordChange',
|
passwordChange = '/sys/user/passwordChange',
|
||||||
//第三方登录
|
//第三方登录
|
||||||
thirdLogin = '/oauth2/token',
|
thirdLogin = '/sys/thirdLogin/getLoginUser',
|
||||||
//第三方登录
|
//第三方登录
|
||||||
getThirdCaptcha = '/sys/thirdSms',
|
getThirdCaptcha = '/sys/thirdSms',
|
||||||
//获取二维码信息
|
//获取二维码信息
|
||||||
|
@ -53,10 +53,6 @@ export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal')
|
||||||
{
|
{
|
||||||
url: Api.Login,
|
url: Api.Login,
|
||||||
params,
|
params,
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'Authorization': 'Basic amVlY2ctY2xpZW50OnNlY3JldA=='
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
errorMessageMode: mode,
|
errorMessageMode: mode,
|
||||||
|
@ -72,13 +68,8 @@ export function phoneLoginApi(params: LoginParams, mode: ErrorMessageMode = 'mod
|
||||||
{
|
{
|
||||||
url: Api.phoneLogin,
|
url: Api.phoneLogin,
|
||||||
params,
|
params,
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'Authorization': 'Basic amVlY2ctY2xpZW50OnNlY3JldA=='
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isTransformResponse: false,
|
|
||||||
errorMessageMode: mode,
|
errorMessageMode: mode,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -180,19 +171,12 @@ export function thirdLogin(params, mode: ErrorMessageMode = 'modal') {
|
||||||
tenantId = params.tenantId;
|
tenantId = params.tenantId;
|
||||||
}
|
}
|
||||||
//==========end 第三方登录/auth2登录需要传递租户id===========
|
//==========end 第三方登录/auth2登录需要传递租户id===========
|
||||||
return defHttp.post<LoginResultModel>(
|
return defHttp.get<LoginResultModel>(
|
||||||
{
|
{
|
||||||
url: `${Api.thirdLogin}`,
|
url: `${Api.thirdLogin}/${params.token}/${params.thirdType}/${tenantId}`,
|
||||||
params,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'Authorization': 'Basic amVlY2ctY2xpZW50OnNlY3JldA=='
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isTransformResponse: false,
|
|
||||||
errorMessageMode: mode,
|
errorMessageMode: mode,
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,7 +151,6 @@ export const useUserStore = defineStore({
|
||||||
): Promise<GetUserInfoModel | null> {
|
): Promise<GetUserInfoModel | null> {
|
||||||
try {
|
try {
|
||||||
const { goHome = true, mode, ...loginParams } = params;
|
const { goHome = true, mode, ...loginParams } = params;
|
||||||
loginParams.grant_type = 'password';
|
|
||||||
const data = await loginApi(loginParams, mode);
|
const data = await loginApi(loginParams, mode);
|
||||||
const { token, userInfo } = data;
|
const { token, userInfo } = data;
|
||||||
// save token
|
// save token
|
||||||
|
@ -249,11 +248,10 @@ export const useUserStore = defineStore({
|
||||||
): Promise<GetUserInfoModel | null> {
|
): Promise<GetUserInfoModel | null> {
|
||||||
try {
|
try {
|
||||||
const { goHome = true, mode, ...loginParams } = params;
|
const { goHome = true, mode, ...loginParams } = params;
|
||||||
loginParams.grant_type = 'phone';
|
|
||||||
const data = await phoneLoginApi(loginParams, mode);
|
const data = await phoneLoginApi(loginParams, mode);
|
||||||
const { access_token } = data;
|
const { token } = data;
|
||||||
// save token
|
// save token
|
||||||
this.setToken(access_token);
|
this.setToken(token);
|
||||||
return this.afterLoginAction(goHome, data);
|
return this.afterLoginAction(goHome, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
@ -358,11 +356,10 @@ export const useUserStore = defineStore({
|
||||||
): Promise<any | null> {
|
): Promise<any | null> {
|
||||||
try {
|
try {
|
||||||
const { goHome = true, mode, ...ThirdLoginParams } = params;
|
const { goHome = true, mode, ...ThirdLoginParams } = params;
|
||||||
ThirdLoginParams.grant_type = "social";
|
|
||||||
const data = await thirdLogin(ThirdLoginParams, mode);
|
const data = await thirdLogin(ThirdLoginParams, mode);
|
||||||
const { access_token } = data;
|
const { token } = data;
|
||||||
// save token
|
// save token
|
||||||
this.setToken(access_token);
|
this.setToken(token);
|
||||||
return this.afterLoginAction(goHome, data);
|
return this.afterLoginAction(goHome, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|
|
@ -165,7 +165,7 @@ const transform: AxiosTransform = {
|
||||||
// update-end--author:liaozhiyang---date:20240509---for:【issues/1220】登录时,vue3版本不加载字典数据设置无效
|
// update-end--author:liaozhiyang---date:20240509---for:【issues/1220】登录时,vue3版本不加载字典数据设置无效
|
||||||
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
|
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
|
||||||
// jwt token
|
// jwt token
|
||||||
config.headers.Authorization = options.authenticationScheme ? `${options.authenticationScheme} ${token}` : 'Bearer ' + token;
|
config.headers.Authorization = options.authenticationScheme ? `${options.authenticationScheme} ${token}` : token;
|
||||||
config.headers[ConfigEnum.TOKEN] = token;
|
config.headers[ConfigEnum.TOKEN] = token;
|
||||||
|
|
||||||
// 将签名和时间戳,添加在请求接口 Header
|
// 将签名和时间戳,添加在请求接口 Header
|
||||||
|
|
|
@ -149,7 +149,6 @@
|
||||||
username: data.account,
|
username: data.account,
|
||||||
captcha: data.inputCode,
|
captcha: data.inputCode,
|
||||||
checkKey: randCodeData.checkKey,
|
checkKey: randCodeData.checkKey,
|
||||||
grant_type: 'password',
|
|
||||||
mode: 'none', //不要默认的错误提示
|
mode: 'none', //不要默认的错误提示
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue