🎉 2.2.0.RELEASE

This commit is contained in:
smallchill 2019-03-31 22:18:59 +08:00
parent 7d088af59f
commit 70a1574a8a
32 changed files with 677 additions and 53 deletions

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<version>2.1.1</version> <version>2.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -29,6 +29,12 @@ spring:
add-mappings: false add-mappings: false
datasource: datasource:
driver-class-name: com.mysql.jdbc.Driver driver-class-name: com.mysql.jdbc.Driver
hikari:
connection-test-query: SELECT 1 FROM DUAL
connection-timeout: 30000
maximum-pool-size: 5
max-lifetime: 1800000
minimum-idle: 1
devtools: devtools:
restart: restart:
log-condition-evaluation-delta: false log-condition-evaluation-delta: false

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.1.1</version> <version>2.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.1.1</version> <version>2.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -93,6 +93,7 @@ public class BladeApplication {
props.setProperty("blade.is-local", String.valueOf(isLocalDev())); props.setProperty("blade.is-local", String.valueOf(isLocalDev()));
props.setProperty("blade.dev-mode", profile.equals(AppConstant.PROD_CODE) ? "false" : "true"); props.setProperty("blade.dev-mode", profile.equals(AppConstant.PROD_CODE) ? "false" : "true");
props.setProperty("blade.service.version", AppConstant.APPLICATION_VERSION); props.setProperty("blade.service.version", AppConstant.APPLICATION_VERSION);
props.setProperty("spring.main.allow-bean-definition-overriding", "true");
props.setProperty("spring.cloud.nacos.discovery.server-addr", NacosConstant.NACOS_ADDR); props.setProperty("spring.cloud.nacos.discovery.server-addr", NacosConstant.NACOS_ADDR);
props.setProperty("spring.cloud.nacos.config.server-addr", NacosConstant.NACOS_ADDR); props.setProperty("spring.cloud.nacos.config.server-addr", NacosConstant.NACOS_ADDR);
props.setProperty("spring.cloud.nacos.config.prefix", NacosConstant.NACOS_CONFIG_PREFIX); props.setProperty("spring.cloud.nacos.config.prefix", NacosConstant.NACOS_CONFIG_PREFIX);

View File

@ -1,3 +1,18 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.launch.constant; package org.springblade.core.launch.constant;
/** /**

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.launch.constant;
/**
* Token配置常量.
*
* @author Chill
*/
public interface TokenConstant {
String SIGN_KEY = "BladeX";
String AVATAR = "avatar";
String HEADER = "blade-auth";
String BEARER = "bearer";
String ACCESS_TOKEN = "access_token";
String REFRESH_TOKEN = "refresh_token";
String TOKEN_TYPE = "token_type";
String EXPIRES_IN = "expires_in";
String ACCOUNT = "account";
String USER_ID = "user_id";
String ROLE_ID = "role_id";
String USER_NAME = "user_name";
String ROLE_NAME = "role_name";
String TENANT_CODE = "tenant_code";
String CLIENT_ID = "client_id";
String LICENSE = "license";
String LICENSE_NAME = "powered by bladex";
String DEFAULT_AVATAR = "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png";
Integer AUTH_LENGTH = 7;
}

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.1.1</version> <version>2.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.1.1</version> <version>2.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.1.1</version> <version>2.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -28,6 +28,17 @@
<artifactId>blade-core-tool</artifactId> <artifactId>blade-core-tool</artifactId>
<version>${blade.tool.version}</version> <version>${blade.tool.version}</version>
</dependency> </dependency>
<!--Jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<artifactId>tomcat-jdbc</artifactId>
<groupId>org.apache.tomcat</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -42,5 +42,5 @@ public class AuthInfo {
@ApiModelProperty(value = "过期时间") @ApiModelProperty(value = "过期时间")
private long expiresIn; private long expiresIn;
@ApiModelProperty(value = "许可证") @ApiModelProperty(value = "许可证")
private String license = "made by blade"; private String license = "powered by blade";
} }

View File

@ -29,6 +29,11 @@ import java.io.Serializable;
public class BladeUser implements Serializable { public class BladeUser implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 客户端id
*/
@ApiModelProperty(hidden = true)
private String clientId;
/** /**
* 用户id * 用户id

View File

@ -20,7 +20,9 @@ import java.lang.annotation.*;
/** /**
* 权限注解 用于检查权限 规定访问权限 * 权限注解 用于检查权限 规定访问权限
* *
* @author Chill * @example @PreAuth("#userVO.id<10")
* @example @PreAuth("hasRole(#test, #test1)")
* @example @PreAuth("hasPermission(#test) and @PreAuth.hasPermission(#test)")
*/ */
@Target({ElementType.METHOD, ElementType.TYPE}) @Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)

View File

@ -24,7 +24,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
/** /**
* secure模块api放行默认配置 * secure注册默认配置
* *
* @author Chill * @author Chill
*/ */

View File

@ -18,32 +18,50 @@ package org.springblade.core.secure.config;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springblade.core.secure.aspect.AuthAspect; import org.springblade.core.secure.aspect.AuthAspect;
import org.springblade.core.secure.interceptor.ClientInterceptor;
import org.springblade.core.secure.interceptor.SecureInterceptor; import org.springblade.core.secure.interceptor.SecureInterceptor;
import org.springblade.core.secure.props.BladeClientProperties;
import org.springblade.core.secure.props.BladeSecureProperties;
import org.springblade.core.secure.provider.ClientDetailsServiceImpl;
import org.springblade.core.secure.provider.IClientDetailsService;
import org.springblade.core.secure.registry.SecureRegistry; import org.springblade.core.secure.registry.SecureRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/** /**
* 配置类 * 安全配置类
* *
* @author Chill * @author Chill
*/ */
@Order @Order
@Configuration @Configuration
@AllArgsConstructor @AllArgsConstructor
@EnableConfigurationProperties({BladeSecureProperties.class, BladeClientProperties.class})
public class SecureConfiguration implements WebMvcConfigurer { public class SecureConfiguration implements WebMvcConfigurer {
private final SecureRegistry secureRegistry; private final SecureRegistry secureRegistry;
private final BladeSecureProperties secureProperties;
private final BladeClientProperties clientProperties;
private final JdbcTemplate jdbcTemplate;
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
clientProperties.getClient().forEach(cs -> registry.addInterceptor(new ClientInterceptor(cs.getClientId())).addPathPatterns(cs.getPathPatterns()));
if (secureRegistry.isEnable()) { if (secureRegistry.isEnable()) {
registry.addInterceptor(new SecureInterceptor()) registry.addInterceptor(new SecureInterceptor())
.excludePathPatterns(secureRegistry.getExcludePatterns()) .excludePathPatterns(secureRegistry.getExcludePatterns())
.excludePathPatterns(secureRegistry.getDefaultExcludePatterns()); .excludePathPatterns(secureRegistry.getDefaultExcludePatterns())
.excludePathPatterns(secureProperties.getExcludePatterns());
} }
} }
@ -52,4 +70,10 @@ public class SecureConfiguration implements WebMvcConfigurer {
return new AuthAspect(); return new AuthAspect();
} }
@Bean
@ConditionalOnMissingBean(IClientDetailsService.class)
public IClientDetailsService clientDetailsService() {
return new ClientDetailsServiceImpl(jdbcTemplate);
}
} }

View File

@ -0,0 +1,55 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.secure.constant;
/**
* 授权校验常量
*
* @author Chill
*/
public interface SecureConstant {
/**
* 认证请求头
*/
String BASIC_HEADER_KEY = "Authorization";
/**
* 认证请求头前缀
*/
String BASIC_HEADER_PREFIX = "Basic ";
/**
* blade_client表字段
*/
String CLIENT_FIELDS = "client_id, client_secret, access_token_validity, refresh_token_validity";
/**
* blade_client查询语句
*/
String BASE_STATEMENT = "select " + CLIENT_FIELDS + " from blade_client";
/**
* blade_client查询排序
*/
String DEFAULT_FIND_STATEMENT = BASE_STATEMENT + " order by client_id";
/**
* 查询client_id
*/
String DEFAULT_SELECT_STATEMENT = BASE_STATEMENT + " where client_id = ?";
}

View File

@ -32,7 +32,7 @@ public class SecureException extends RuntimeException {
public SecureException(String message) { public SecureException(String message) {
super(message); super(message);
this.resultCode = ResultCode.INTERNAL_SERVER_ERROR; this.resultCode = ResultCode.UN_AUTHORIZED;
} }
public SecureException(IResultCode resultCode) { public SecureException(IResultCode resultCode) {

View File

@ -0,0 +1,67 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.secure.interceptor;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.SecureUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.api.ResultCode;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
/**
* 客户端校验
*
* @author Chill
*/
@Slf4j
@AllArgsConstructor
public class ClientInterceptor extends HandlerInterceptorAdapter {
private final String clientId;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
BladeUser user = SecureUtil.getUser();
if (user != null && StringUtil.equals(clientId, SecureUtil.getClientIdFromHeader()) && StringUtil.equals(clientId, user.getClientId())) {
return true;
} else {
log.warn("客户端认证失败,请求接口:{}请求IP{},请求参数:{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
R result = R.fail(ResultCode.UN_AUTHORIZED);
response.setHeader(BladeConstant.CONTENT_TYPE_NAME, MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setCharacterEncoding(BladeConstant.UTF_8);
response.setStatus(HttpServletResponse.SC_OK);
try {
response.getWriter().write(Objects.requireNonNull(JsonUtil.toJson(result)));
} catch (IOException ex) {
log.error(ex.getMessage());
}
return false;
}
}
}

View File

@ -15,13 +15,13 @@
*/ */
package org.springblade.core.secure.interceptor; package org.springblade.core.secure.interceptor;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springblade.core.secure.utils.SecureUtil; import org.springblade.core.secure.utils.SecureUtil;
import org.springblade.core.tool.api.R; import org.springblade.core.tool.api.R;
import org.springblade.core.tool.api.ResultCode; import org.springblade.core.tool.api.ResultCode;
import org.springblade.core.tool.constant.BladeConstant; import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.WebUtil; import org.springblade.core.tool.utils.WebUtil;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
@ -37,6 +37,7 @@ import java.util.Objects;
* @author Chill * @author Chill
*/ */
@Slf4j @Slf4j
@AllArgsConstructor
public class SecureInterceptor extends HandlerInterceptorAdapter { public class SecureInterceptor extends HandlerInterceptorAdapter {
@Override @Override
@ -46,7 +47,7 @@ public class SecureInterceptor extends HandlerInterceptorAdapter {
} else { } else {
log.warn("签名认证失败,请求接口:{}请求IP{},请求参数:{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap())); log.warn("签名认证失败,请求接口:{}请求IP{},请求参数:{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
R result = R.fail(ResultCode.UN_AUTHORIZED); R result = R.fail(ResultCode.UN_AUTHORIZED);
response.setCharacterEncoding(StringPool.UTF_8); response.setCharacterEncoding(BladeConstant.UTF_8);
response.setHeader(BladeConstant.CONTENT_TYPE_NAME, MediaType.APPLICATION_JSON_UTF8_VALUE); response.setHeader(BladeConstant.CONTENT_TYPE_NAME, MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setStatus(HttpServletResponse.SC_OK); response.setStatus(HttpServletResponse.SC_OK);
try { try {

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.secure.props;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* 客户端校验配置
*
* @author Chill
*/
@Data
@ConfigurationProperties("blade.secure")
public class BladeClientProperties {
private final List<ClientSecure> client = new ArrayList<>();
}

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.secure.props;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* secure放行额外配置
*
* @author Chill
*/
@Data
@ConfigurationProperties("blade.secure.url")
public class BladeSecureProperties {
private final List<String> excludePatterns = new ArrayList<>();
}

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.secure.props;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 客户端令牌认证信息
*
* @author Chill
*/
@Data
public class ClientSecure {
private String clientId;
private final List<String> pathPatterns = new ArrayList<>();
}

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.secure.provider;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 客户端详情
*
* @author Chill
*/
@Data
public class ClientDetails implements IClientDetails {
/**
* 客户端id
*/
@ApiModelProperty(value = "客户端id")
private String clientId;
/**
* 客户端密钥
*/
@ApiModelProperty(value = "客户端密钥")
private String clientSecret;
/**
* 令牌过期秒数
*/
@ApiModelProperty(value = "令牌过期秒数")
private Integer accessTokenValidity;
/**
* 刷新令牌过期秒数
*/
@ApiModelProperty(value = "刷新令牌过期秒数")
private Integer refreshTokenValidity;
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.secure.provider;
import lombok.AllArgsConstructor;
import org.springblade.core.secure.constant.SecureConstant;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 获取客户端详情
*
* @author Chill
*/
@AllArgsConstructor
public class ClientDetailsServiceImpl implements IClientDetailsService {
private final JdbcTemplate jdbcTemplate;
@Override
public IClientDetails loadClientByClientId(String clientId) {
try {
return jdbcTemplate.queryForObject(SecureConstant.DEFAULT_SELECT_STATEMENT, new String[]{clientId}, new BeanPropertyRowMapper<>(ClientDetails.class));
} catch (Exception ex) {
return null;
}
}
}

View File

@ -0,0 +1,55 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.secure.provider;
import java.io.Serializable;
/**
* 多终端详情接口
*
* @author Chill
*/
public interface IClientDetails extends Serializable {
/**
* 客户端id.
*
* @return String.
*/
String getClientId();
/**
* 客户端密钥.
*
* @return String.
*/
String getClientSecret();
/**
* 客户端token过期时间
*
* @return Integer
*/
Integer getAccessTokenValidity();
/**
* 客户端刷新token过期时间
*
* @return Integer
*/
Integer getRefreshTokenValidity();
}

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.springblade.core.secure.provider;
/**
* 多终端注册接口
*
* @author Chill
*/
public interface IClientDetailsService {
/**
* 根据clientId获取Client详情
*
* @param clientId 客户端id
* @return
*/
IClientDetails loadClientByClientId(String clientId);
}

View File

@ -50,9 +50,6 @@ public class SecureRegistry {
/** /**
* 设置放行api * 设置放行api
*
* @param patterns api配置
* @return SecureRegistry
*/ */
public SecureRegistry excludePathPatterns(String... patterns) { public SecureRegistry excludePathPatterns(String... patterns) {
return excludePathPatterns(Arrays.asList(patterns)); return excludePathPatterns(Arrays.asList(patterns));
@ -60,9 +57,6 @@ public class SecureRegistry {
/** /**
* 设置放行api * 设置放行api
*
* @param patterns api配置
* @return SecureRegistry
*/ */
public SecureRegistry excludePathPatterns(List<String> patterns) { public SecureRegistry excludePathPatterns(List<String> patterns) {
this.excludePatterns.addAll(patterns); this.excludePatterns.addAll(patterns);

View File

@ -19,19 +19,19 @@ import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import lombok.SneakyThrows;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.secure.BladeUser; import org.springblade.core.secure.BladeUser;
import org.springblade.core.tool.utils.Charsets; import org.springblade.core.secure.constant.SecureConstant;
import org.springblade.core.tool.utils.Func; import org.springblade.core.secure.exception.SecureException;
import org.springblade.core.tool.utils.StringPool; import org.springblade.core.secure.provider.IClientDetails;
import org.springblade.core.tool.utils.WebUtil; import org.springblade.core.secure.provider.IClientDetailsService;
import org.springblade.core.tool.utils.*;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.security.Key; import java.security.Key;
import java.util.Base64; import java.util.*;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
/** /**
* Secure工具类 * Secure工具类
@ -39,18 +39,25 @@ import java.util.Map;
* @author Chill * @author Chill
*/ */
public class SecureUtil { public class SecureUtil {
public static final String BLADE_USER_REQUEST_ATTR = "_BLADE_USER_REQUEST_ATTR_"; private static final String BLADE_USER_REQUEST_ATTR = "_BLADE_USER_REQUEST_ATTR_";
public final static String HEADER = "blade-auth"; private final static String HEADER = TokenConstant.HEADER;
public final static String BEARER = "bearer"; private final static String BEARER = TokenConstant.BEARER;
public final static String ACCOUNT = "account"; private final static String ACCOUNT = TokenConstant.ACCOUNT;
public final static String USER_ID = "userId"; private final static String USER_ID = TokenConstant.USER_ID;
public final static String ROLE_ID = "roleId"; private final static String ROLE_ID = TokenConstant.ROLE_ID;
public final static String USER_NAME = "userName"; private final static String USER_NAME = TokenConstant.USER_NAME;
public final static String ROLE_NAME = "roleName"; private final static String ROLE_NAME = TokenConstant.ROLE_NAME;
public final static String TENANT_CODE = "tenantCode"; private final static String TENANT_CODE = TokenConstant.TENANT_CODE;
public final static Integer AUTH_LENGTH = 7; private final static String CLIENT_ID = TokenConstant.CLIENT_ID;
public static String BASE64_SECURITY = Base64.getEncoder().encodeToString("BladeX".getBytes(Charsets.UTF_8)); private final static Integer AUTH_LENGTH = TokenConstant.AUTH_LENGTH;
private static String BASE64_SECURITY = Base64.getEncoder().encodeToString(TokenConstant.SIGN_KEY.getBytes(Charsets.UTF_8));
private static IClientDetailsService clientDetailsService;
static {
clientDetailsService = SpringUtil.getBean(IClientDetailsService.class);
}
/** /**
* 获取用户信息 * 获取用户信息
@ -59,8 +66,11 @@ public class SecureUtil {
*/ */
public static BladeUser getUser() { public static BladeUser getUser() {
HttpServletRequest request = WebUtil.getRequest(); HttpServletRequest request = WebUtil.getRequest();
if (request == null) {
return null;
}
// 优先从 request 中获取 // 优先从 request 中获取
BladeUser bladeUser = (BladeUser) request.getAttribute(BLADE_USER_REQUEST_ATTR); Object bladeUser = request.getAttribute(BLADE_USER_REQUEST_ATTR);
if (bladeUser == null) { if (bladeUser == null) {
bladeUser = getUser(request); bladeUser = getUser(request);
if (bladeUser != null) { if (bladeUser != null) {
@ -68,7 +78,7 @@ public class SecureUtil {
request.setAttribute(BLADE_USER_REQUEST_ATTR, bladeUser); request.setAttribute(BLADE_USER_REQUEST_ATTR, bladeUser);
} }
} }
return bladeUser; return (BladeUser) bladeUser;
} }
/** /**
@ -82,6 +92,7 @@ public class SecureUtil {
if (claims == null) { if (claims == null) {
return null; return null;
} }
String clientId = Func.toStr(claims.get(SecureUtil.CLIENT_ID));
Integer userId = Func.toInt(claims.get(SecureUtil.USER_ID)); Integer userId = Func.toInt(claims.get(SecureUtil.USER_ID));
String tenantCode = Func.toStr(claims.get(SecureUtil.TENANT_CODE)); String tenantCode = Func.toStr(claims.get(SecureUtil.TENANT_CODE));
String roleId = Func.toStr(claims.get(SecureUtil.ROLE_ID)); String roleId = Func.toStr(claims.get(SecureUtil.ROLE_ID));
@ -89,6 +100,7 @@ public class SecureUtil {
String roleName = Func.toStr(claims.get(SecureUtil.ROLE_NAME)); String roleName = Func.toStr(claims.get(SecureUtil.ROLE_NAME));
String userName = Func.toStr(claims.get(SecureUtil.USER_NAME)); String userName = Func.toStr(claims.get(SecureUtil.USER_NAME));
BladeUser bladeUser = new BladeUser(); BladeUser bladeUser = new BladeUser();
bladeUser.setClientId(clientId);
bladeUser.setUserId(userId); bladeUser.setUserId(userId);
bladeUser.setTenantCode(tenantCode); bladeUser.setTenantCode(tenantCode);
bladeUser.setAccount(account); bladeUser.setAccount(account);
@ -183,7 +195,6 @@ public class SecureUtil {
return (null == user) ? StringPool.EMPTY : user.getRoleName(); return (null == user) ? StringPool.EMPTY : user.getRoleName();
} }
/** /**
* 获取租户编号 * 获取租户编号
* *
@ -205,6 +216,27 @@ public class SecureUtil {
return (null == user) ? StringPool.EMPTY : user.getTenantCode(); return (null == user) ? StringPool.EMPTY : user.getTenantCode();
} }
/**
* 获取客户端id
*
* @return tenantCode
*/
public static String getClientId() {
BladeUser user = getUser();
return (null == user) ? StringPool.EMPTY : user.getClientId();
}
/**
* 获取客户端id
*
* @param request request
* @return tenantCode
*/
public static String getClientId(HttpServletRequest request) {
BladeUser user = getUser(request);
return (null == user) ? StringPool.EMPTY : user.getClientId();
}
/** /**
* 获取Claims * 获取Claims
* *
@ -229,7 +261,7 @@ public class SecureUtil {
* @return header * @return header
*/ */
public static String getHeader() { public static String getHeader() {
return getHeader(WebUtil.getRequest()); return getHeader(Objects.requireNonNull(WebUtil.getRequest()));
} }
/** /**
@ -250,17 +282,16 @@ public class SecureUtil {
*/ */
public static Claims parseJWT(String jsonWebToken) { public static Claims parseJWT(String jsonWebToken) {
try { try {
Claims claims = Jwts.parser() return Jwts.parser()
.setSigningKey(Base64.getDecoder().decode(BASE64_SECURITY)) .setSigningKey(Base64.getDecoder().decode(BASE64_SECURITY))
.parseClaimsJws(jsonWebToken).getBody(); .parseClaimsJws(jsonWebToken).getBody();
return claims;
} catch (Exception ex) { } catch (Exception ex) {
return null; return null;
} }
} }
/** /**
* 创建jwt * 创建令牌
* *
* @param user user * @param user user
* @param audience audience * @param audience audience
@ -269,6 +300,17 @@ public class SecureUtil {
* @return jwt * @return jwt
*/ */
public static String createJWT(Map<String, String> user, String audience, String issuer, boolean isExpire) { public static String createJWT(Map<String, String> user, String audience, String issuer, boolean isExpire) {
String[] tokens = extractAndDecodeHeader();
assert tokens.length == 2;
String clientId = tokens[0];
String clientSecret = tokens[1];
// 校验客户端信息
if (!validateClient(clientId, clientSecret)) {
throw new SecureException("客户端认证失败!");
}
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis(); long nowMillis = System.currentTimeMillis();
@ -287,6 +329,9 @@ public class SecureUtil {
//设置JWT参数 //设置JWT参数
user.forEach(builder::claim); user.forEach(builder::claim);
//设置应用id
builder.claim(CLIENT_ID, clientId);
//添加Token过期时间 //添加Token过期时间
if (isExpire) { if (isExpire) {
long expMillis = nowMillis + getExpire(); long expMillis = nowMillis + getExpire();
@ -313,4 +358,65 @@ public class SecureUtil {
return cal.getTimeInMillis() - System.currentTimeMillis(); return cal.getTimeInMillis() - System.currentTimeMillis();
} }
/**
* 获取过期时间的秒数(次日凌晨3点)
*
* @return expire
*/
public static int getExpireSeconds() {
return (int) (getExpire() / 1000);
}
/**
* 客户端信息解码
*/
@SneakyThrows
public static String[] extractAndDecodeHeader() {
// 获取请求头客户端信息
String header = Objects.requireNonNull(WebUtil.getRequest()).getHeader(SecureConstant.BASIC_HEADER_KEY);
if (header == null || !header.startsWith(SecureConstant.BASIC_HEADER_PREFIX)) {
throw new SecureException("No client information in request header");
}
byte[] base64Token = header.substring(6).getBytes(Charsets.UTF_8_NAME);
byte[] decoded;
try {
decoded = Base64.getDecoder().decode(base64Token);
} catch (IllegalArgumentException var7) {
throw new RuntimeException("Failed to decode basic authentication token");
}
String token = new String(decoded, Charsets.UTF_8_NAME);
int index = token.indexOf(StringPool.COLON);
if (index == -1) {
throw new RuntimeException("Invalid basic authentication token");
} else {
return new String[]{token.substring(0, index), token.substring(index + 1)};
}
}
/**
* 获取请求头中的客户端id
*/
public static String getClientIdFromHeader() {
String[] tokens = extractAndDecodeHeader();
assert tokens.length == 2;
return tokens[0];
}
/**
* 校验Client
*
* @param clientId 客户端id
* @param clientSecret 客户端密钥
* @return boolean
*/
private static boolean validateClient(String clientId, String clientSecret) {
IClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (clientDetails != null) {
return StringUtil.equals(clientId, clientDetails.getClientId()) && StringUtil.equals(clientSecret, clientDetails.getClientSecret());
}
return false;
}
} }

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<version>2.1.1</version> <version>2.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<version>2.1.1</version> <version>2.2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -16,6 +16,7 @@
package org.springblade.core.tool.utils; package org.springblade.core.tool.utils;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException; import java.nio.charset.UnsupportedCharsetException;
@ -29,15 +30,20 @@ public class Charsets {
/** /**
* 字符集ISO-8859-1 * 字符集ISO-8859-1
*/ */
public static final java.nio.charset.Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
public static final String ISO_8859_1_NAME = ISO_8859_1.name();
/** /**
* 字符集GBK * 字符集GBK
*/ */
public static final java.nio.charset.Charset GBK = java.nio.charset.Charset.forName(StringPool.GBK); public static final Charset GBK = Charset.forName(StringPool.GBK);
public static final String GBK_NAME = GBK.name();
/** /**
* 字符集utf-8 * 字符集utf-8
*/ */
public static final java.nio.charset.Charset UTF_8 = StandardCharsets.UTF_8; public static final Charset UTF_8 = StandardCharsets.UTF_8;
public static final String UTF_8_NAME = UTF_8.name();
/** /**
* 转换为Charset对象 * 转换为Charset对象

View File

@ -5,7 +5,7 @@
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId> <artifactId>blade-tool</artifactId>
<version>2.1.1</version> <version>2.2.0</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>blade-tool</name> <name>blade-tool</name>
<description> <description>
@ -36,7 +36,7 @@
</scm> </scm>
<properties> <properties>
<blade.tool.version>2.1.1</blade.tool.version> <blade.tool.version>2.2.0</blade.tool.version>
<java.version>1.8</java.version> <java.version>1.8</java.version>
<maven.plugin.version>3.8.0</maven.plugin.version> <maven.plugin.version>3.8.0</maven.plugin.version>