1. 引入 redis,消除 JWT 无法手动过期的缺陷 2. 保证一个用户同一时间只能保证一个设备登录 3. 支持“记住我”操作,开启后,JWT过期时间默认为7天pull/1/head
@@ -39,6 +39,17 @@ | |||
<artifactId>spring-boot-starter-data-jpa</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-data-redis</artifactId> | |||
</dependency> | |||
<!-- 对象池,使用redis时必须引入 --> | |||
<dependency> | |||
<groupId>org.apache.commons</groupId> | |||
<artifactId>commons-pool2</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-configuration-processor</artifactId> | |||
@@ -16,8 +16,8 @@ import lombok.EqualsAndHashCode; | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Data | |||
@EqualsAndHashCode(callSuper = true) | |||
@Data | |||
public class BaseException extends RuntimeException { | |||
private Integer code; | |||
private String message; | |||
@@ -32,4 +32,9 @@ public interface Consts { | |||
* 按钮 | |||
*/ | |||
Integer BUTTON = 2; | |||
/** | |||
* JWT 在 Redis 中保存的key前缀 | |||
*/ | |||
String REDIS_JWT_KEY_PREFIX = "security:jwt:"; | |||
} |
@@ -18,49 +18,54 @@ import lombok.Getter; | |||
@Getter | |||
public enum Status implements IStatus { | |||
/** | |||
* 操作成功 | |||
* 操作成功! | |||
*/ | |||
SUCCESS(200, "操作成功"), | |||
SUCCESS(200, "操作成功!"), | |||
/** | |||
* 操作异常 | |||
* 操作异常! | |||
*/ | |||
ERROR(500, "操作异常"), | |||
ERROR(500, "操作异常!"), | |||
/** | |||
* 退出成功 | |||
* 退出成功! | |||
*/ | |||
LOGOUT(200, "退出成功"), | |||
LOGOUT(200, "退出成功!"), | |||
/** | |||
* 请先登录 | |||
* 请先登录! | |||
*/ | |||
UNAUTHORIZED(401, "请先登录"), | |||
UNAUTHORIZED(401, "请先登录!"), | |||
/** | |||
* 暂无权限访问 | |||
* 暂无权限访问! | |||
*/ | |||
ACCESS_DENIED(403, "权限不足"), | |||
ACCESS_DENIED(403, "权限不足!"), | |||
/** | |||
* 请求不存在 | |||
* 请求不存在! | |||
*/ | |||
REQUEST_NOT_FOUND(404, "请求不存在"), | |||
REQUEST_NOT_FOUND(404, "请求不存在!"), | |||
/** | |||
* 请求异常 | |||
* 请求方式不支持! | |||
*/ | |||
BAD_REQUEST(400, "请求异常"), | |||
HTTP_BAD_METHOD(405, "请求方式不支持!"), | |||
/** | |||
* 参数不匹配 | |||
* 请求异常! | |||
*/ | |||
PARAM_NOT_MATCH(400, "参数不匹配"), | |||
BAD_REQUEST(400, "请求异常!"), | |||
/** | |||
* 参数不能为空 | |||
* 参数不匹配! | |||
*/ | |||
PARAM_NOT_NULL(400, "参数不能为空"), | |||
PARAM_NOT_MATCH(400, "参数不匹配!"), | |||
/** | |||
* 参数不能为空! | |||
*/ | |||
PARAM_NOT_NULL(400, "参数不能为空!"), | |||
/** | |||
* 当前用户已被锁定,请联系管理员解锁! | |||
@@ -68,19 +73,24 @@ public enum Status implements IStatus { | |||
USER_DISABLED(403, "当前用户已被锁定,请联系管理员解锁!"), | |||
/** | |||
* 用户名或密码错误 | |||
* 用户名或密码错误! | |||
*/ | |||
USERNAME_PASSWORD_ERROR(5001, "用户名或密码错误!"), | |||
/** | |||
* token 已过期,请重新登录! | |||
*/ | |||
USERNAME_PASSWORD_ERROR(5001, "用户名或密码错误"), | |||
TOKEN_EXPIRED(5002, "token 已过期,请重新登录!"), | |||
/** | |||
* token 已过期,请重新登录 | |||
* token 解析失败,请尝试重新登录! | |||
*/ | |||
TOKEN_EXPIRED(5002, "token 已过期,请重新登录"), | |||
TOKEN_PARSE_ERROR(5002, "token 解析失败,请尝试重新登录!"), | |||
/** | |||
* token 解析失败,请尝试重新登录 | |||
* 当前用户已在别处登录,请尝试更改密码或重新登录! | |||
*/ | |||
TOKEN_PARSE_ERROR(5002, "token 解析失败,请尝试重新登录"); | |||
TOKEN_OUT_OF_CTRL(5003,"当前用户已在别处登录,请尝试更改密码或重新登录!"); | |||
/** | |||
* 状态码 | |||
@@ -50,7 +50,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { | |||
if (antPathMatcher.match("/**/api/auth/**", request.getRequestURI())) { | |||
filterChain.doFilter(request, response); | |||
} else { | |||
String jwt = getJwtFromRequest(request); | |||
String jwt = jwtUtil.getJwtFromRequest(request); | |||
if (StrUtil.isNotBlank(jwt)) { | |||
try { | |||
@@ -72,11 +72,4 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { | |||
} | |||
} | |||
private String getJwtFromRequest(HttpServletRequest request) { | |||
String bearerToken = request.getHeader("Authorization"); | |||
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) { | |||
return bearerToken.substring(7); | |||
} | |||
return null; | |||
} | |||
} |
@@ -20,12 +20,17 @@ import org.springframework.boot.context.properties.ConfigurationProperties; | |||
@Data | |||
public class JwtConfig { | |||
/** | |||
* jwt 加密 key, 默认值:xkcoding. | |||
* jwt 加密 key,默认值:xkcoding. | |||
*/ | |||
private String key = "xkcoding"; | |||
/** | |||
* jwt 过期时间, 默认值:600000 {@code 10 分钟}. | |||
* jwt 过期时间,默认值:600000 {@code 10 分钟}. | |||
*/ | |||
private Long ttl = 600000L; | |||
/** | |||
* 开启 记住我 之后 jwt 过期时间,默认值 604800000 {@code 7 天} | |||
*/ | |||
private Long remember = 604800000L; | |||
} |
@@ -55,13 +55,16 @@ public class RbacAuthorityService { | |||
//获取资源,前后端分离,所以过滤页面权限,只保留按钮权限 | |||
List<Permission> btnPerms = permissions.stream() | |||
// 过滤页面权限 | |||
.filter(permission -> Objects.equals(permission.getType(), Consts.BUTTON)) | |||
.filter(permission -> StrUtil.isNotBlank(permission.getPermission())) | |||
// 过滤 URL 为空 | |||
.filter(permission -> StrUtil.isNotBlank(permission.getUrl())) | |||
// 过滤 METHOD 为空 | |||
.filter(permission -> StrUtil.isNotBlank(permission.getMethod())) | |||
.collect(Collectors.toList()); | |||
for (Permission btnPerm : btnPerms) { | |||
AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(btnPerm.getPermission(), btnPerm.getMethod()); | |||
AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(btnPerm.getUrl(), btnPerm.getMethod()); | |||
if (antPathMatcher.matches(request)) { | |||
hasPermission = true; | |||
break; | |||
@@ -0,0 +1,44 @@ | |||
package com.xkcoding.rbac.security.config; | |||
import org.springframework.boot.autoconfigure.AutoConfigureAfter; | |||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; | |||
import org.springframework.cache.annotation.EnableCaching; | |||
import org.springframework.context.annotation.Bean; | |||
import org.springframework.context.annotation.Configuration; | |||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; | |||
import org.springframework.data.redis.core.RedisTemplate; | |||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; | |||
import org.springframework.data.redis.serializer.StringRedisSerializer; | |||
import java.io.Serializable; | |||
/** | |||
* <p> | |||
* redis配置 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.rbac.security.config | |||
* @description: redis配置 | |||
* @author: yangkai.shen | |||
* @date: Created in 2018-12-11 15:16 | |||
* @copyright: Copyright (c) 2018 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Configuration | |||
@AutoConfigureAfter(RedisAutoConfiguration.class) | |||
@EnableCaching | |||
public class RedisConfig { | |||
/** | |||
* 默认情况下的模板只能支持RedisTemplate<String, String>,也就是只能存入字符串,因此支持序列化 | |||
*/ | |||
@Bean | |||
public RedisTemplate<String, Serializable> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) { | |||
RedisTemplate<String, Serializable> template = new RedisTemplate<>(); | |||
template.setKeySerializer(new StringRedisSerializer()); | |||
template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); | |||
template.setConnectionFactory(redisConnectionFactory); | |||
return template; | |||
} | |||
} |
@@ -32,9 +32,6 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; | |||
@Configuration | |||
@EnableWebSecurity | |||
public class SecurityConfig extends WebSecurityConfigurerAdapter { | |||
@Autowired | |||
private LogoutSuccessHandler logoutSuccessHandler; | |||
@Autowired | |||
private AccessDeniedHandler accessDeniedHandler; | |||
@@ -70,6 +67,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { | |||
.csrf() | |||
.disable() | |||
// 登录行为由自己实现,参考 AuthController#login | |||
.formLogin() | |||
.disable() | |||
.httpBasic() | |||
@@ -86,17 +84,11 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { | |||
.anyRequest() | |||
.access("@rbacAuthorityService.hasPermission(request,authentication)") | |||
// 登出处理 | |||
// 登出行为由自己实现,参考 AuthController#logout | |||
.and() | |||
.logout() | |||
// 登出请求默认为POST请求,改为GET请求 | |||
.logoutRequestMatcher(new AntPathRequestMatcher("/**/logout", "GET")) | |||
// 登出成功处理器 | |||
.logoutSuccessHandler(logoutSuccessHandler) | |||
.permitAll() | |||
.logout().disable() | |||
// Session 管理 | |||
.and() | |||
.sessionManagement() | |||
// 因为使用了JWT,所以这里不管理Session | |||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) | |||
@@ -5,7 +5,6 @@ import com.xkcoding.rbac.security.util.ResponseUtil; | |||
import org.springframework.context.annotation.Bean; | |||
import org.springframework.context.annotation.Configuration; | |||
import org.springframework.security.web.access.AccessDeniedHandler; | |||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; | |||
/** | |||
* <p> | |||
@@ -23,18 +22,9 @@ import org.springframework.security.web.authentication.logout.LogoutSuccessHandl | |||
@Configuration | |||
public class SecurityHandlerConfig { | |||
/** | |||
* 退出成功处理器 | |||
* | |||
* @return 退出成功处理器 | |||
*/ | |||
@Bean | |||
public LogoutSuccessHandler logoutSuccessHandler() { | |||
return (request, response, authentication) -> ResponseUtil.renderJson(response, Status.LOGOUT, null); | |||
} | |||
@Bean | |||
public AccessDeniedHandler accessDeniedHandler() { | |||
return (request, response, accessDeniedException) -> ResponseUtil.renderJson(response, Status.ACCESS_DENIED, null); | |||
} | |||
} |
@@ -1,11 +1,14 @@ | |||
package com.xkcoding.rbac.security.controller; | |||
import com.xkcoding.rbac.security.common.ApiResponse; | |||
import com.xkcoding.rbac.security.common.Status; | |||
import com.xkcoding.rbac.security.exception.SecurityException; | |||
import com.xkcoding.rbac.security.payload.LoginRequest; | |||
import com.xkcoding.rbac.security.util.JwtUtil; | |||
import com.xkcoding.rbac.security.vo.JwtResponse; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.data.redis.core.StringRedisTemplate; | |||
import org.springframework.security.authentication.AuthenticationManager; | |||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | |||
import org.springframework.security.core.Authentication; | |||
@@ -15,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestBody; | |||
import org.springframework.web.bind.annotation.RequestMapping; | |||
import org.springframework.web.bind.annotation.RestController; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.validation.Valid; | |||
/** | |||
@@ -51,7 +55,18 @@ public class AuthController { | |||
SecurityContextHolder.getContext() | |||
.setAuthentication(authentication); | |||
String jwt = jwtUtil.createJWT(authentication); | |||
String jwt = jwtUtil.createJWT(authentication,loginRequest.getRememberMe()); | |||
return ApiResponse.ofSuccess(new JwtResponse(jwt)); | |||
} | |||
@PostMapping("/logout") | |||
public ApiResponse logout(HttpServletRequest request) { | |||
try { | |||
// 设置JWT过期 | |||
jwtUtil.invalidateJWT(request); | |||
} catch (SecurityException e) { | |||
throw new SecurityException(Status.UNAUTHORIZED); | |||
} | |||
return ApiResponse.ofStatus(Status.LOGOUT); | |||
} | |||
} |
@@ -2,7 +2,8 @@ package com.xkcoding.rbac.security.exception; | |||
import com.xkcoding.rbac.security.common.BaseException; | |||
import com.xkcoding.rbac.security.common.Status; | |||
import lombok.Getter; | |||
import lombok.Data; | |||
import lombok.EqualsAndHashCode; | |||
/** | |||
* <p> | |||
@@ -17,7 +18,8 @@ import lombok.Getter; | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Getter | |||
@EqualsAndHashCode(callSuper = true) | |||
@Data | |||
public class SecurityException extends BaseException { | |||
public SecurityException(Status status) { | |||
super(status); | |||
@@ -1,6 +1,7 @@ | |||
package com.xkcoding.rbac.security.exception.handler; | |||
import cn.hutool.core.collection.CollUtil; | |||
import cn.hutool.json.JSONUtil; | |||
import com.xkcoding.rbac.security.common.ApiResponse; | |||
import com.xkcoding.rbac.security.common.BaseException; | |||
import com.xkcoding.rbac.security.common.Status; | |||
@@ -8,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; | |||
import org.springframework.http.converter.HttpMessageNotReadableException; | |||
import org.springframework.security.authentication.BadCredentialsException; | |||
import org.springframework.security.authentication.DisabledException; | |||
import org.springframework.web.HttpRequestMethodNotSupportedException; | |||
import org.springframework.web.bind.MethodArgumentNotValidException; | |||
import org.springframework.web.bind.annotation.ControllerAdvice; | |||
import org.springframework.web.bind.annotation.ExceptionHandler; | |||
@@ -40,6 +42,9 @@ public class GlobalExceptionHandler { | |||
if (e instanceof NoHandlerFoundException) { | |||
log.error("【全局异常拦截】NoHandlerFoundException: 请求方法 {}, 请求路径 {}", ((NoHandlerFoundException) e).getRequestURL(), ((NoHandlerFoundException) e).getHttpMethod()); | |||
return ApiResponse.ofStatus(Status.REQUEST_NOT_FOUND); | |||
} else if (e instanceof HttpRequestMethodNotSupportedException) { | |||
log.error("【全局异常拦截】HttpRequestMethodNotSupportedException: 当前请求方式 {}, 支持请求方式 {}", ((HttpRequestMethodNotSupportedException) e).getMethod(), JSONUtil.toJsonStr(((HttpRequestMethodNotSupportedException) e).getSupportedHttpMethods())); | |||
return ApiResponse.ofStatus(Status.HTTP_BAD_METHOD); | |||
} else if (e instanceof MethodArgumentNotValidException) { | |||
log.error("【全局异常拦截】MethodArgumentNotValidException", e); | |||
return ApiResponse.of(Status.BAD_REQUEST.getCode(), ((MethodArgumentNotValidException) e).getBindingResult() | |||
@@ -23,13 +23,18 @@ public class LoginRequest { | |||
/** | |||
* 用户名或邮箱或手机号 | |||
*/ | |||
@NotBlank | |||
@NotBlank(message = "用户名不能为空") | |||
private String usernameOrEmailOrPhone; | |||
/** | |||
* 密码 | |||
*/ | |||
@NotBlank | |||
@NotBlank(message = "密码不能为空") | |||
private String password; | |||
/** | |||
* 记住我 | |||
*/ | |||
private Boolean rememberMe = false; | |||
} |
@@ -1,6 +1,8 @@ | |||
package com.xkcoding.rbac.security.util; | |||
import cn.hutool.core.date.DateUtil; | |||
import cn.hutool.core.util.StrUtil; | |||
import com.xkcoding.rbac.security.common.Consts; | |||
import com.xkcoding.rbac.security.common.Status; | |||
import com.xkcoding.rbac.security.config.JwtConfig; | |||
import com.xkcoding.rbac.security.exception.SecurityException; | |||
@@ -10,12 +12,16 @@ import lombok.extern.slf4j.Slf4j; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.boot.context.properties.EnableConfigurationProperties; | |||
import org.springframework.context.annotation.Configuration; | |||
import org.springframework.data.redis.core.StringRedisTemplate; | |||
import org.springframework.security.core.Authentication; | |||
import org.springframework.security.core.GrantedAuthority; | |||
import javax.servlet.http.HttpServletRequest; | |||
import java.util.Collection; | |||
import java.util.Date; | |||
import java.util.List; | |||
import java.util.Objects; | |||
import java.util.concurrent.TimeUnit; | |||
/** | |||
* <p> | |||
@@ -34,23 +40,23 @@ import java.util.List; | |||
@Configuration | |||
@Slf4j | |||
public class JwtUtil { | |||
private final JwtConfig jwtConfig; | |||
@Autowired | |||
private JwtConfig jwtConfig; | |||
@Autowired | |||
public JwtUtil(JwtConfig jwtConfig) { | |||
this.jwtConfig = jwtConfig; | |||
} | |||
private StringRedisTemplate stringRedisTemplate; | |||
/** | |||
* 创建JWT | |||
* | |||
* @param rememberMe 记住我 | |||
* @param id 用户id | |||
* @param subject 用户名 | |||
* @param roles 用户角色 | |||
* @param authorities 用户权限 | |||
* @return JWT | |||
*/ | |||
public String createJWT(Long id, String subject, List<String> roles, Collection<? extends GrantedAuthority> authorities) { | |||
public String createJWT(Boolean rememberMe, Long id, String subject, List<String> roles, Collection<? extends GrantedAuthority> authorities) { | |||
Date now = new Date(); | |||
JwtBuilder builder = Jwts.builder() | |||
.setId(id.toString()) | |||
@@ -59,22 +65,30 @@ public class JwtUtil { | |||
.signWith(SignatureAlgorithm.HS256, jwtConfig.getKey()) | |||
.claim("roles", roles) | |||
.claim("authorities", authorities); | |||
if (jwtConfig.getTtl() > 0) { | |||
builder.setExpiration(DateUtil.offsetMillisecond(now, jwtConfig.getTtl() | |||
.intValue())); | |||
// 设置过期时间 | |||
Long ttl = rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl(); | |||
if (ttl > 0) { | |||
builder.setExpiration(DateUtil.offsetMillisecond(now, ttl.intValue())); | |||
} | |||
return builder.compact(); | |||
String jwt = builder.compact(); | |||
// 将生成的JWT保存至Redis | |||
stringRedisTemplate.opsForValue() | |||
.set(Consts.REDIS_JWT_KEY_PREFIX + subject, jwt, ttl, TimeUnit.MILLISECONDS); | |||
return jwt; | |||
} | |||
/** | |||
* 创建JWT | |||
* | |||
* @param authentication 用户认证信息 | |||
* @param rememberMe 记住我 | |||
* @return JWT | |||
*/ | |||
public String createJWT(Authentication authentication) { | |||
public String createJWT(Authentication authentication, Boolean rememberMe) { | |||
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); | |||
return createJWT(userPrincipal.getId(), userPrincipal.getUsername(), userPrincipal.getRoles(), userPrincipal.getAuthorities()); | |||
return createJWT(rememberMe, userPrincipal.getId(), userPrincipal.getUsername(), userPrincipal.getRoles(), userPrincipal.getAuthorities()); | |||
} | |||
/** | |||
@@ -85,10 +99,27 @@ public class JwtUtil { | |||
*/ | |||
public Claims parseJWT(String jwt) { | |||
try { | |||
return Jwts.parser() | |||
Claims claims = Jwts.parser() | |||
.setSigningKey(jwtConfig.getKey()) | |||
.parseClaimsJws(jwt) | |||
.getBody(); | |||
String username = claims.getSubject(); | |||
String redisKey = Consts.REDIS_JWT_KEY_PREFIX + username; | |||
// 校验redis中的JWT是否存在 | |||
Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS); | |||
if (Objects.isNull(expire) || expire <= 0) { | |||
throw new SecurityException(Status.TOKEN_EXPIRED); | |||
} | |||
// 校验redis中的JWT是否与当前的一致,不一致则代表用户已注销/用户在不同设备登录,均代表JWT已过期 | |||
String redisToken = stringRedisTemplate.opsForValue() | |||
.get(redisKey); | |||
if (!StrUtil.equals(jwt, redisToken)) { | |||
throw new SecurityException(Status.TOKEN_OUT_OF_CTRL); | |||
} | |||
return claims; | |||
} catch (ExpiredJwtException e) { | |||
log.error("Token 已过期"); | |||
throw new SecurityException(Status.TOKEN_EXPIRED); | |||
@@ -107,6 +138,18 @@ public class JwtUtil { | |||
} | |||
} | |||
/** | |||
* 设置JWT过期 | |||
* | |||
* @param request 请求 | |||
*/ | |||
public void invalidateJWT(HttpServletRequest request) { | |||
String jwt = getJwtFromRequest(request); | |||
String username = getUsernameFromJWT(jwt); | |||
// 从redis中清除JWT | |||
stringRedisTemplate.delete(Consts.REDIS_JWT_KEY_PREFIX + username); | |||
} | |||
/** | |||
* 根据 jwt 获取用户名 | |||
* | |||
@@ -118,4 +161,18 @@ public class JwtUtil { | |||
return claims.getSubject(); | |||
} | |||
/** | |||
* 从 request 的 header 中获取 JWT | |||
* | |||
* @param request 请求 | |||
* @return JWT | |||
*/ | |||
public String getJwtFromRequest(HttpServletRequest request) { | |||
String bearerToken = request.getHeader("Authorization"); | |||
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) { | |||
return bearerToken.substring(7); | |||
} | |||
return null; | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
package com.xkcoding.rbac.security.util; | |||
import cn.hutool.json.JSONObject; | |||
import cn.hutool.json.JSONUtil; | |||
import com.xkcoding.rbac.security.common.ApiResponse; | |||
import com.xkcoding.rbac.security.common.BaseException; | |||
@@ -39,8 +40,10 @@ public class ResponseUtil { | |||
response.setContentType("application/json;charset=UTF-8"); | |||
response.setStatus(200); | |||
// FIXME: hutool 的 BUG:JSONUtil.toJsonStr() | |||
// 将JSON转为String的时候,忽略null值的时候转成的String存在错误 | |||
response.getWriter() | |||
.write(JSONUtil.toJsonStr(ApiResponse.ofStatus(status, data))); | |||
.write(JSONUtil.toJsonStr(new JSONObject(ApiResponse.ofStatus(status, data), true))); | |||
} catch (IOException e) { | |||
log.error("Response写出JSON异常,", e); | |||
} | |||
@@ -59,8 +62,10 @@ public class ResponseUtil { | |||
response.setContentType("application/json;charset=UTF-8"); | |||
response.setStatus(200); | |||
// FIXME: hutool 的 BUG:JSONUtil.toJsonStr() | |||
// 将JSON转为String的时候,忽略null值的时候转成的String存在错误 | |||
response.getWriter() | |||
.write(JSONUtil.toJsonStr(ApiResponse.ofException(exception))); | |||
.write(JSONUtil.toJsonStr(new JSONObject(ApiResponse.ofException(exception), true))); | |||
} catch (IOException e) { | |||
log.error("Response写出JSON异常,", e); | |||
} | |||
@@ -18,7 +18,29 @@ spring: | |||
properties: | |||
hibernate: | |||
dialect: org.hibernate.dialect.MySQL57InnoDBDialect | |||
resources: | |||
add-mappings: false | |||
mvc: | |||
throw-exception-if-no-handler-found: true | |||
redis: | |||
host: localhost | |||
port: 6379 | |||
# 连接超时时间(记得添加单位,Duration) | |||
timeout: 10000ms | |||
# Redis默认情况下有16个分片,这里配置具体使用的分片 | |||
# database: 0 | |||
lettuce: | |||
pool: | |||
# 连接池最大连接数(使用负值表示没有限制) 默认 8 | |||
max-active: 8 | |||
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 | |||
max-wait: -1ms | |||
# 连接池中的最大空闲连接 默认 8 | |||
max-idle: 8 | |||
# 连接池中的最小空闲连接 默认 0 | |||
min-idle: 0 | |||
jwt: | |||
config: | |||
key: xkcoding | |||
ttl: 600000 | |||
remember: 604800000 |