mirror of https://github.com/elunez/eladmin
refactor: 优化安全配置和代码结构,移除冗余类,SecurityUtils优化获取用户名与用户ID方式
parent
75df46b5dc
commit
51d9f42273
|
@ -22,7 +22,7 @@ import lombok.Data;
|
||||||
* @date 2018-11-23
|
* @date 2018-11-23
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
class ApiError {
|
public class ApiError {
|
||||||
|
|
||||||
private Integer status = 400;
|
private Integer status = 400;
|
||||||
private Long timestamp;
|
private Long timestamp;
|
||||||
|
|
|
@ -15,18 +15,24 @@
|
||||||
*/
|
*/
|
||||||
package me.zhengjie.utils;
|
package me.zhengjie.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.jwt.JWT;
|
||||||
|
import cn.hutool.jwt.JWTUtil;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.alibaba.fastjson.JSONArray;
|
import com.alibaba.fastjson.JSONArray;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.zhengjie.exception.BadRequestException;
|
|
||||||
import me.zhengjie.utils.enums.DataScopeEnum;
|
import me.zhengjie.utils.enums.DataScopeEnum;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前登录的用户
|
* 获取当前登录的用户
|
||||||
|
@ -34,8 +40,23 @@ import java.util.List;
|
||||||
* @date 2019-01-17
|
* @date 2019-01-17
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@Component
|
||||||
public class SecurityUtils {
|
public class SecurityUtils {
|
||||||
|
|
||||||
|
public static String header;
|
||||||
|
|
||||||
|
public static String tokenStartWith;
|
||||||
|
|
||||||
|
@Value("${jwt.header}")
|
||||||
|
public void setHeader(String header) {
|
||||||
|
SecurityUtils.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value("${jwt.token-start-with}")
|
||||||
|
public void setTokenStartWith(String tokenStartWith) {
|
||||||
|
SecurityUtils.tokenStartWith = tokenStartWith;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前登录的用户
|
* 获取当前登录的用户
|
||||||
* @return UserDetails
|
* @return UserDetails
|
||||||
|
@ -45,34 +66,6 @@ public class SecurityUtils {
|
||||||
return userDetailsService.loadUserByUsername(getCurrentUsername());
|
return userDetailsService.loadUserByUsername(getCurrentUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统用户名称
|
|
||||||
*
|
|
||||||
* @return 系统用户名称
|
|
||||||
*/
|
|
||||||
public static String getCurrentUsername() {
|
|
||||||
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
if (authentication == null) {
|
|
||||||
throw new BadRequestException(HttpStatus.UNAUTHORIZED, "当前登录状态过期");
|
|
||||||
}
|
|
||||||
if (authentication.getPrincipal() instanceof UserDetails) {
|
|
||||||
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
|
||||||
return userDetails.getUsername();
|
|
||||||
}
|
|
||||||
throw new BadRequestException(HttpStatus.UNAUTHORIZED, "找不到当前登录的信息");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统用户ID
|
|
||||||
* @return 系统用户ID
|
|
||||||
*/
|
|
||||||
public static Long getCurrentUserId() {
|
|
||||||
UserDetails userDetails = getCurrentUser();
|
|
||||||
// 将 Java 对象转换为 JSONObject 对象
|
|
||||||
JSONObject jsonObject = (JSONObject) JSON.toJSON(userDetails);
|
|
||||||
return jsonObject.getJSONObject("user").getLong("id");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前用户的数据权限
|
* 获取当前用户的数据权限
|
||||||
* @return /
|
* @return /
|
||||||
|
@ -91,9 +84,62 @@ public class SecurityUtils {
|
||||||
*/
|
*/
|
||||||
public static String getDataScopeType() {
|
public static String getDataScopeType() {
|
||||||
List<Long> dataScopes = getCurrentUserDataScope();
|
List<Long> dataScopes = getCurrentUserDataScope();
|
||||||
if(dataScopes.size() != 0){
|
if(CollUtil.isEmpty(dataScopes)){
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return DataScopeEnum.ALL.getValue();
|
return DataScopeEnum.ALL.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户ID
|
||||||
|
* @return 系统用户ID
|
||||||
|
*/
|
||||||
|
public static Long getCurrentUserId() {
|
||||||
|
return getCurrentUserId(getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户ID
|
||||||
|
* @return 系统用户ID
|
||||||
|
*/
|
||||||
|
public static Long getCurrentUserId(String token) {
|
||||||
|
JWT jwt = JWTUtil.parseToken(token);
|
||||||
|
return Long.valueOf(jwt.getPayload("userId").toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统用户名称
|
||||||
|
*
|
||||||
|
* @return 系统用户名称
|
||||||
|
*/
|
||||||
|
public static String getCurrentUsername() {
|
||||||
|
return getCurrentUsername(getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统用户名称
|
||||||
|
*
|
||||||
|
* @return 系统用户名称
|
||||||
|
*/
|
||||||
|
public static String getCurrentUsername(String token) {
|
||||||
|
JWT jwt = JWTUtil.parseToken(token);
|
||||||
|
return jwt.getPayload("sub").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Token
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static String getToken() {
|
||||||
|
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder
|
||||||
|
.getRequestAttributes())).getRequest();
|
||||||
|
String bearerToken = request.getHeader(header);
|
||||||
|
if (bearerToken != null && bearerToken.startsWith(tokenStartWith)) {
|
||||||
|
// 去掉令牌前缀
|
||||||
|
return bearerToken.replace(tokenStartWith, "");
|
||||||
|
} else {
|
||||||
|
log.debug("非法Token:{}", bearerToken);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,89 +13,100 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package me.zhengjie.modules.security.config.bean;
|
package me.zhengjie.modules.security.config;
|
||||||
|
|
||||||
import com.wf.captcha.*;
|
import com.wf.captcha.*;
|
||||||
import com.wf.captcha.base.Captcha;
|
import com.wf.captcha.base.Captcha;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import me.zhengjie.exception.BadConfigurationException;
|
import lombok.Getter;
|
||||||
|
import me.zhengjie.exception.BadRequestException;
|
||||||
|
import me.zhengjie.modules.security.config.enums.LoginCodeEnum;
|
||||||
import me.zhengjie.utils.StringUtils;
|
import me.zhengjie.utils.StringUtils;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置文件读取
|
* 登录验证码配置信息
|
||||||
*
|
|
||||||
* @author liaojinlong
|
* @author liaojinlong
|
||||||
* @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6
|
* @date 2025-01-13
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class LoginProperties {
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "login.code")
|
||||||
|
public class CaptchaConfig {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 账号单用户 登录
|
* 验证码配置
|
||||||
*/
|
*/
|
||||||
private boolean singleLogin = false;
|
@Getter
|
||||||
|
private LoginCodeEnum codeType;
|
||||||
private LoginCode loginCode;
|
|
||||||
|
|
||||||
public static final String cacheKey = "user-login-cache:";
|
|
||||||
|
|
||||||
public boolean isSingleLogin() {
|
|
||||||
return singleLogin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取验证码生产类
|
* 验证码有效期 分钟
|
||||||
*
|
|
||||||
* @return /
|
|
||||||
*/
|
*/
|
||||||
public Captcha getCaptcha() {
|
private Long expiration = 5L;
|
||||||
if (Objects.isNull(loginCode)) {
|
|
||||||
loginCode = new LoginCode();
|
/**
|
||||||
if (Objects.isNull(loginCode.getCodeType())) {
|
* 验证码内容长度
|
||||||
loginCode.setCodeType(LoginCodeEnum.ARITHMETIC);
|
*/
|
||||||
}
|
private int length = 4;
|
||||||
}
|
|
||||||
return switchCaptcha(loginCode);
|
/**
|
||||||
}
|
* 验证码宽度
|
||||||
|
*/
|
||||||
|
private int width = 111;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码高度
|
||||||
|
*/
|
||||||
|
private int height = 36;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码字体
|
||||||
|
*/
|
||||||
|
private String fontName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字体大小
|
||||||
|
*/
|
||||||
|
private int fontSize = 25;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 依据配置信息生产验证码
|
* 依据配置信息生产验证码
|
||||||
*
|
|
||||||
* @param loginCode 验证码配置信息
|
|
||||||
* @return /
|
* @return /
|
||||||
*/
|
*/
|
||||||
private Captcha switchCaptcha(LoginCode loginCode) {
|
public Captcha getCaptcha() {
|
||||||
Captcha captcha;
|
Captcha captcha;
|
||||||
switch (loginCode.getCodeType()) {
|
switch (codeType) {
|
||||||
case ARITHMETIC:
|
case ARITHMETIC:
|
||||||
// 算术类型 https://gitee.com/whvse/EasyCaptcha
|
// 算术类型 https://gitee.com/whvse/EasyCaptcha
|
||||||
captcha = new FixedArithmeticCaptcha(loginCode.getWidth(), loginCode.getHeight());
|
captcha = new FixedArithmeticCaptcha(width, height);
|
||||||
// 几位数运算,默认是两位
|
// 几位数运算,默认是两位
|
||||||
captcha.setLen(loginCode.getLength());
|
captcha.setLen(length);
|
||||||
break;
|
break;
|
||||||
case CHINESE:
|
case CHINESE:
|
||||||
captcha = new ChineseCaptcha(loginCode.getWidth(), loginCode.getHeight());
|
captcha = new ChineseCaptcha(width, height);
|
||||||
captcha.setLen(loginCode.getLength());
|
captcha.setLen(length);
|
||||||
break;
|
break;
|
||||||
case CHINESE_GIF:
|
case CHINESE_GIF:
|
||||||
captcha = new ChineseGifCaptcha(loginCode.getWidth(), loginCode.getHeight());
|
captcha = new ChineseGifCaptcha(width, height);
|
||||||
captcha.setLen(loginCode.getLength());
|
captcha.setLen(length);
|
||||||
break;
|
break;
|
||||||
case GIF:
|
case GIF:
|
||||||
captcha = new GifCaptcha(loginCode.getWidth(), loginCode.getHeight());
|
captcha = new GifCaptcha(width, height);
|
||||||
captcha.setLen(loginCode.getLength());
|
captcha.setLen(length);
|
||||||
break;
|
break;
|
||||||
case SPEC:
|
case SPEC:
|
||||||
captcha = new SpecCaptcha(loginCode.getWidth(), loginCode.getHeight());
|
captcha = new SpecCaptcha(width, height);
|
||||||
captcha.setLen(loginCode.getLength());
|
captcha.setLen(length);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new BadConfigurationException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
|
throw new BadRequestException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
|
||||||
}
|
}
|
||||||
if(StringUtils.isNotBlank(loginCode.getFontName())){
|
if(StringUtils.isNotBlank(fontName)){
|
||||||
captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN, loginCode.getFontSize()));
|
captcha.setFont(new Font(fontName, Font.PLAIN, fontSize));
|
||||||
}
|
}
|
||||||
return captcha;
|
return captcha;
|
||||||
}
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019-2020 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package me.zhengjie.modules.security.config;
|
|
||||||
|
|
||||||
import me.zhengjie.modules.security.config.bean.LoginProperties;
|
|
||||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @apiNote 配置文件转换Pojo类的 统一配置 类
|
|
||||||
* @author: liaojinlong
|
|
||||||
* @date: 2020/6/10 19:04
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class ConfigBeanConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConfigurationProperties(prefix = "login")
|
|
||||||
public LoginProperties loginProperties() {
|
|
||||||
return new LoginProperties();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConfigurationProperties(prefix = "jwt")
|
|
||||||
public SecurityProperties securityProperties() {
|
|
||||||
return new SecurityProperties();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019-2020 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version loginCode.length.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-loginCode.length.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package me.zhengjie.modules.security.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置文件读取
|
||||||
|
*
|
||||||
|
* @author liaojinlong
|
||||||
|
* @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "login")
|
||||||
|
public class LoginProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号单用户 登录
|
||||||
|
*/
|
||||||
|
private boolean singleLogin = false;
|
||||||
|
|
||||||
|
public static final String cacheKey = "user-login-cache:";
|
||||||
|
}
|
|
@ -13,9 +13,11 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package me.zhengjie.modules.security.config.bean;
|
package me.zhengjie.modules.security.config;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jwt参数配置
|
* Jwt参数配置
|
||||||
|
@ -24,6 +26,8 @@ import lombok.Data;
|
||||||
* @date 2019年11月28日
|
* @date 2019年11月28日
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "jwt")
|
||||||
public class SecurityProperties {
|
public class SecurityProperties {
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -16,7 +16,6 @@
|
||||||
package me.zhengjie.modules.security.config;
|
package me.zhengjie.modules.security.config;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
|
||||||
import me.zhengjie.modules.security.security.*;
|
import me.zhengjie.modules.security.security.*;
|
||||||
import me.zhengjie.modules.security.service.OnlineUserService;
|
import me.zhengjie.modules.security.service.OnlineUserService;
|
||||||
import me.zhengjie.modules.security.service.UserCacheManager;
|
import me.zhengjie.modules.security.service.UserCacheManager;
|
||||||
|
@ -34,7 +33,6 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
import org.springframework.web.filter.CorsFilter;
|
import org.springframework.web.filter.CorsFilter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
@ -54,7 +52,6 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
private final SecurityProperties properties;
|
private final SecurityProperties properties;
|
||||||
private final OnlineUserService onlineUserService;
|
private final OnlineUserService onlineUserService;
|
||||||
private final UserCacheManager userCacheManager;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
||||||
|
@ -75,7 +72,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
httpSecurity
|
httpSecurity
|
||||||
// 禁用 CSRF
|
// 禁用 CSRF
|
||||||
.csrf().disable()
|
.csrf().disable()
|
||||||
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
|
.addFilter(corsFilter)
|
||||||
// 授权异常
|
// 授权异常
|
||||||
.exceptionHandling()
|
.exceptionHandling()
|
||||||
.authenticationEntryPoint(authenticationErrorHandler)
|
.authenticationEntryPoint(authenticationErrorHandler)
|
||||||
|
@ -131,6 +128,6 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private TokenConfigurer securityConfigurerAdapter() {
|
private TokenConfigurer securityConfigurerAdapter() {
|
||||||
return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheManager);
|
return new TokenConfigurer(tokenProvider, properties, onlineUserService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019-2020 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package me.zhengjie.modules.security.config.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录验证码配置信息
|
|
||||||
*
|
|
||||||
* @author liaojinlong
|
|
||||||
* @date 2020/6/10 18:53
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class LoginCode {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证码配置
|
|
||||||
*/
|
|
||||||
private LoginCodeEnum codeType;
|
|
||||||
/**
|
|
||||||
* 验证码有效期 分钟
|
|
||||||
*/
|
|
||||||
private Long expiration = 2L;
|
|
||||||
/**
|
|
||||||
* 验证码内容长度
|
|
||||||
*/
|
|
||||||
private int length = 2;
|
|
||||||
/**
|
|
||||||
* 验证码宽度
|
|
||||||
*/
|
|
||||||
private int width = 111;
|
|
||||||
/**
|
|
||||||
* 验证码高度
|
|
||||||
*/
|
|
||||||
private int height = 36;
|
|
||||||
/**
|
|
||||||
* 验证码字体
|
|
||||||
*/
|
|
||||||
private String fontName;
|
|
||||||
/**
|
|
||||||
* 字体大小
|
|
||||||
*/
|
|
||||||
private int fontSize = 25;
|
|
||||||
|
|
||||||
public LoginCodeEnum getCodeType() {
|
|
||||||
return codeType;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package me.zhengjie.modules.security.config.bean;
|
package me.zhengjie.modules.security.config.enums;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证码配置枚举
|
* 验证码配置枚举
|
|
@ -27,10 +27,12 @@ import me.zhengjie.annotation.rest.AnonymousGetMapping;
|
||||||
import me.zhengjie.annotation.rest.AnonymousPostMapping;
|
import me.zhengjie.annotation.rest.AnonymousPostMapping;
|
||||||
import me.zhengjie.config.properties.RsaProperties;
|
import me.zhengjie.config.properties.RsaProperties;
|
||||||
import me.zhengjie.exception.BadRequestException;
|
import me.zhengjie.exception.BadRequestException;
|
||||||
import me.zhengjie.modules.security.config.bean.LoginCodeEnum;
|
import me.zhengjie.modules.security.config.CaptchaConfig;
|
||||||
import me.zhengjie.modules.security.config.bean.LoginProperties;
|
import me.zhengjie.modules.security.config.enums.LoginCodeEnum;
|
||||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
import me.zhengjie.modules.security.config.LoginProperties;
|
||||||
|
import me.zhengjie.modules.security.config.SecurityProperties;
|
||||||
import me.zhengjie.modules.security.security.TokenProvider;
|
import me.zhengjie.modules.security.security.TokenProvider;
|
||||||
|
import me.zhengjie.modules.security.service.UserDetailsServiceImpl;
|
||||||
import me.zhengjie.modules.security.service.dto.AuthUserDto;
|
import me.zhengjie.modules.security.service.dto.AuthUserDto;
|
||||||
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
||||||
import me.zhengjie.modules.security.service.OnlineUserService;
|
import me.zhengjie.modules.security.service.OnlineUserService;
|
||||||
|
@ -41,13 +43,12 @@ import me.zhengjie.utils.StringUtils;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import javax.annotation.Resource;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -63,14 +64,15 @@ import java.util.concurrent.TimeUnit;
|
||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Api(tags = "系统:系统授权接口")
|
@Api(tags = "系统:系统授权接口")
|
||||||
public class AuthorizationController {
|
public class AuthController {
|
||||||
private final SecurityProperties properties;
|
private final SecurityProperties properties;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final OnlineUserService onlineUserService;
|
private final OnlineUserService onlineUserService;
|
||||||
private final TokenProvider tokenProvider;
|
private final TokenProvider tokenProvider;
|
||||||
private final AuthenticationManagerBuilder authenticationManagerBuilder;
|
private final LoginProperties loginProperties;
|
||||||
@Resource
|
private final CaptchaConfig captchaConfig;
|
||||||
private LoginProperties loginProperties;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final UserDetailsServiceImpl userDetailsService;
|
||||||
|
|
||||||
@Log("用户登录")
|
@Log("用户登录")
|
||||||
@ApiOperation("登录授权")
|
@ApiOperation("登录授权")
|
||||||
|
@ -88,27 +90,29 @@ public class AuthorizationController {
|
||||||
if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
|
if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
|
||||||
throw new BadRequestException("验证码错误");
|
throw new BadRequestException("验证码错误");
|
||||||
}
|
}
|
||||||
UsernamePasswordAuthenticationToken authenticationToken =
|
// 获取用户信息
|
||||||
new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
|
JwtUserDto jwtUser = userDetailsService.loadUserByUsername(authUser.getUsername());
|
||||||
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
|
// 验证用户密码
|
||||||
|
if (!passwordEncoder.matches(password, jwtUser.getPassword())) {
|
||||||
|
throw new BadRequestException("登录密码错误");
|
||||||
|
}
|
||||||
|
Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities());
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
// 生成令牌与第三方系统获取令牌方式
|
// 生成令牌
|
||||||
// UserDetails userDetails = userDetailsService.loadUserByUsername(userInfo.getUsername());
|
String token = tokenProvider.createToken(jwtUser);
|
||||||
// Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
// 将密码设置为空
|
||||||
// SecurityContextHolder.getContext().setAuthentication(authentication);
|
jwtUser.setPassword(null);
|
||||||
String token = tokenProvider.createToken(authentication);
|
|
||||||
final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
|
|
||||||
// 返回 token 与 用户信息
|
// 返回 token 与 用户信息
|
||||||
Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
|
Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
|
||||||
put("token", properties.getTokenStartWith() + token);
|
put("token", properties.getTokenStartWith() + token);
|
||||||
put("user", jwtUserDto);
|
put("user", jwtUser);
|
||||||
}};
|
}};
|
||||||
if (loginProperties.isSingleLogin()) {
|
if (loginProperties.isSingleLogin()) {
|
||||||
// 踢掉之前已经登录的token
|
// 踢掉之前已经登录的token
|
||||||
onlineUserService.kickOutForUsername(authUser.getUsername());
|
onlineUserService.kickOutForUsername(authUser.getUsername());
|
||||||
}
|
}
|
||||||
// 保存在线信息
|
// 保存在线信息
|
||||||
onlineUserService.save(jwtUserDto, token, request);
|
onlineUserService.save(jwtUser, token, request);
|
||||||
// 返回登录信息
|
// 返回登录信息
|
||||||
return ResponseEntity.ok(authInfo);
|
return ResponseEntity.ok(authInfo);
|
||||||
}
|
}
|
||||||
|
@ -116,14 +120,17 @@ public class AuthorizationController {
|
||||||
@ApiOperation("获取用户信息")
|
@ApiOperation("获取用户信息")
|
||||||
@GetMapping(value = "/info")
|
@GetMapping(value = "/info")
|
||||||
public ResponseEntity<UserDetails> getUserInfo() {
|
public ResponseEntity<UserDetails> getUserInfo() {
|
||||||
return ResponseEntity.ok(SecurityUtils.getCurrentUser());
|
JwtUserDto jwtUser = (JwtUserDto) SecurityUtils.getCurrentUser();
|
||||||
|
// 将密码设置为空
|
||||||
|
jwtUser.setPassword(null);
|
||||||
|
return ResponseEntity.ok(jwtUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation("获取验证码")
|
@ApiOperation("获取验证码")
|
||||||
@AnonymousGetMapping(value = "/code")
|
@AnonymousGetMapping(value = "/code")
|
||||||
public ResponseEntity<Object> getCode() {
|
public ResponseEntity<Object> getCode() {
|
||||||
// 获取运算的结果
|
// 获取运算的结果
|
||||||
Captcha captcha = loginProperties.getCaptcha();
|
Captcha captcha = captchaConfig.getCaptcha();
|
||||||
String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
|
String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
|
||||||
//当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型
|
//当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型
|
||||||
String captchaValue = captcha.text();
|
String captchaValue = captcha.text();
|
||||||
|
@ -131,7 +138,7 @@ public class AuthorizationController {
|
||||||
captchaValue = captchaValue.split("\\.")[0];
|
captchaValue = captchaValue.split("\\.")[0];
|
||||||
}
|
}
|
||||||
// 保存
|
// 保存
|
||||||
redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
|
redisUtils.set(uuid, captchaValue, captchaConfig.getExpiration(), TimeUnit.MINUTES);
|
||||||
// 验证码信息
|
// 验证码信息
|
||||||
Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
|
Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
|
||||||
put("img", captcha.toBase64());
|
put("img", captcha.toBase64());
|
|
@ -15,6 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package me.zhengjie.modules.security.security;
|
package me.zhengjie.modules.security.security;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import me.zhengjie.exception.handler.ApiError;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
@ -32,6 +35,10 @@ public class JwtAccessDeniedHandler implements AccessDeniedHandler {
|
||||||
@Override
|
@Override
|
||||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
|
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
|
||||||
//当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
|
//当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
|
||||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
|
response.setStatus(HttpStatus.FORBIDDEN.value());
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
String jsonResponse = objectMapper.writeValueAsString(ApiError.error(HttpStatus.FORBIDDEN.value(), "禁止访问,您没有权限访问此资源"));
|
||||||
|
response.getWriter().write(jsonResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,13 @@
|
||||||
*/
|
*/
|
||||||
package me.zhengjie.modules.security.security;
|
package me.zhengjie.modules.security.security;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.zhengjie.exception.handler.ApiError;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -26,14 +29,18 @@ import java.io.IOException;
|
||||||
/**
|
/**
|
||||||
* @author Zheng Jie
|
* @author Zheng Jie
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void commence(HttpServletRequest request,
|
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
|
||||||
HttpServletResponse response,
|
|
||||||
AuthenticationException authException) throws IOException {
|
|
||||||
// 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
|
// 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException==null?"Unauthorized":authException.getMessage());
|
int code = HttpStatus.UNAUTHORIZED.value();
|
||||||
|
response.setStatus(code);
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
String jsonResponse = objectMapper.writeValueAsString(ApiError.error(HttpStatus.UNAUTHORIZED.value(), "登录状态已过期,请重新登录"));
|
||||||
|
response.getWriter().write(jsonResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
package me.zhengjie.modules.security.security;
|
package me.zhengjie.modules.security.security;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
import me.zhengjie.modules.security.config.SecurityProperties;
|
||||||
import me.zhengjie.modules.security.service.OnlineUserService;
|
import me.zhengjie.modules.security.service.OnlineUserService;
|
||||||
import me.zhengjie.modules.security.service.UserCacheManager;
|
import me.zhengjie.modules.security.service.UserCacheManager;
|
||||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
|
||||||
|
@ -33,11 +33,10 @@ public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFi
|
||||||
private final TokenProvider tokenProvider;
|
private final TokenProvider tokenProvider;
|
||||||
private final SecurityProperties properties;
|
private final SecurityProperties properties;
|
||||||
private final OnlineUserService onlineUserService;
|
private final OnlineUserService onlineUserService;
|
||||||
private final UserCacheManager userCacheManager;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(HttpSecurity http) {
|
public void configure(HttpSecurity http) {
|
||||||
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService, userCacheManager);
|
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService);
|
||||||
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
|
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,7 @@
|
||||||
package me.zhengjie.modules.security.security;
|
package me.zhengjie.modules.security.security;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import io.jsonwebtoken.ExpiredJwtException;
|
import me.zhengjie.modules.security.config.SecurityProperties;
|
||||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
|
||||||
import me.zhengjie.modules.security.service.UserCacheManager;
|
|
||||||
import me.zhengjie.modules.security.service.dto.OnlineUserDto;
|
import me.zhengjie.modules.security.service.dto.OnlineUserDto;
|
||||||
import me.zhengjie.modules.security.service.OnlineUserService;
|
import me.zhengjie.modules.security.service.OnlineUserService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -33,7 +31,6 @@ import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author /
|
* @author /
|
||||||
|
@ -45,19 +42,16 @@ public class TokenFilter extends GenericFilterBean {
|
||||||
private final TokenProvider tokenProvider;
|
private final TokenProvider tokenProvider;
|
||||||
private final SecurityProperties properties;
|
private final SecurityProperties properties;
|
||||||
private final OnlineUserService onlineUserService;
|
private final OnlineUserService onlineUserService;
|
||||||
private final UserCacheManager userCacheManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param tokenProvider Token
|
* @param tokenProvider Token
|
||||||
* @param properties JWT
|
* @param properties JWT
|
||||||
* @param onlineUserService 用户在线
|
* @param onlineUserService 用户在线
|
||||||
* @param userCacheManager 用户缓存工具
|
|
||||||
*/
|
*/
|
||||||
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheManager userCacheManager) {
|
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
this.onlineUserService = onlineUserService;
|
this.onlineUserService = onlineUserService;
|
||||||
this.tokenProvider = tokenProvider;
|
this.tokenProvider = tokenProvider;
|
||||||
this.userCacheManager = userCacheManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -66,25 +60,17 @@ public class TokenFilter extends GenericFilterBean {
|
||||||
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
|
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
|
||||||
String token = resolveToken(httpServletRequest);
|
String token = resolveToken(httpServletRequest);
|
||||||
// 对于 Token 为空的不需要去查 Redis
|
// 对于 Token 为空的不需要去查 Redis
|
||||||
if (StrUtil.isNotBlank(token)) {
|
if(StrUtil.isNotBlank(token)){
|
||||||
OnlineUserDto onlineUserDto = null;
|
// 获取用户Token的Key
|
||||||
boolean cleanUserCache = false;
|
String loginKey = tokenProvider.loginKey(token);
|
||||||
try {
|
OnlineUserDto onlineUserDto = onlineUserService.getOne(loginKey);
|
||||||
String loginKey = tokenProvider.loginKey(token);
|
// 判断用户在线信息是否为空
|
||||||
onlineUserDto = onlineUserService.getOne(loginKey);
|
if (onlineUserDto != null) {
|
||||||
} catch (ExpiredJwtException e) {
|
// Token 续期判断
|
||||||
log.error(e.getMessage());
|
tokenProvider.checkRenewal(token);
|
||||||
cleanUserCache = true;
|
// 获取认证信息,设置上下文
|
||||||
} finally {
|
|
||||||
if (cleanUserCache || Objects.isNull(onlineUserDto)) {
|
|
||||||
userCacheManager.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (onlineUserDto != null && StringUtils.hasText(token)) {
|
|
||||||
Authentication authentication = tokenProvider.getAuthentication(token);
|
Authentication authentication = tokenProvider.getAuthentication(token);
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
// Token 续期
|
|
||||||
tokenProvider.checkRenewal(token);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filterChain.doFilter(servletRequest, servletResponse);
|
filterChain.doFilter(servletRequest, servletResponse);
|
||||||
|
|
|
@ -23,7 +23,8 @@ import io.jsonwebtoken.*;
|
||||||
import io.jsonwebtoken.io.Decoders;
|
import io.jsonwebtoken.io.Decoders;
|
||||||
import io.jsonwebtoken.security.Keys;
|
import io.jsonwebtoken.security.Keys;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
import me.zhengjie.modules.security.config.SecurityProperties;
|
||||||
|
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
||||||
import me.zhengjie.utils.RedisUtils;
|
import me.zhengjie.utils.RedisUtils;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
@ -42,11 +43,12 @@ import java.util.concurrent.TimeUnit;
|
||||||
@Component
|
@Component
|
||||||
public class TokenProvider implements InitializingBean {
|
public class TokenProvider implements InitializingBean {
|
||||||
|
|
||||||
private final SecurityProperties properties;
|
|
||||||
private final RedisUtils redisUtils;
|
|
||||||
public static final String AUTHORITIES_KEY = "user";
|
|
||||||
private JwtParser jwtParser;
|
private JwtParser jwtParser;
|
||||||
private JwtBuilder jwtBuilder;
|
private JwtBuilder jwtBuilder;
|
||||||
|
private final RedisUtils redisUtils;
|
||||||
|
private final SecurityProperties properties;
|
||||||
|
public static final String AUTHORITIES_UUID_KEY = "uuid";
|
||||||
|
public static final String AUTHORITIES_UID_KEY = "userId";
|
||||||
|
|
||||||
public TokenProvider(SecurityProperties properties, RedisUtils redisUtils) {
|
public TokenProvider(SecurityProperties properties, RedisUtils redisUtils) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
|
@ -68,15 +70,19 @@ public class TokenProvider implements InitializingBean {
|
||||||
* 创建Token 设置永不过期,
|
* 创建Token 设置永不过期,
|
||||||
* Token 的时间有效性转到Redis 维护
|
* Token 的时间有效性转到Redis 维护
|
||||||
*
|
*
|
||||||
* @param authentication /
|
* @param user /
|
||||||
* @return /
|
* @return /
|
||||||
*/
|
*/
|
||||||
public String createToken(Authentication authentication) {
|
public String createToken(JwtUserDto user) {
|
||||||
|
// 设置参数
|
||||||
|
Map<String, Object> claims = new HashMap<>(6);
|
||||||
|
// 设置用户ID
|
||||||
|
claims.put(AUTHORITIES_UID_KEY, user.getUser().getId());
|
||||||
|
// 设置UUID,确保每次Token不一样
|
||||||
|
claims.put(AUTHORITIES_UUID_KEY, IdUtil.simpleUUID());
|
||||||
return jwtBuilder
|
return jwtBuilder
|
||||||
// 加入ID确保生成的 Token 都不一致
|
.setClaims(claims)
|
||||||
.setId(IdUtil.simpleUUID())
|
.setSubject(user.getUsername())
|
||||||
.claim(AUTHORITIES_KEY, authentication.getName())
|
|
||||||
.setSubject(authentication.getName())
|
|
||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,11 @@ import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.zhengjie.modules.security.security.TokenProvider;
|
import me.zhengjie.modules.security.security.TokenProvider;
|
||||||
import me.zhengjie.utils.PageResult;
|
import me.zhengjie.utils.PageResult;
|
||||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
import me.zhengjie.modules.security.config.SecurityProperties;
|
||||||
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
||||||
import me.zhengjie.modules.security.service.dto.OnlineUserDto;
|
import me.zhengjie.modules.security.service.dto.OnlineUserDto;
|
||||||
import me.zhengjie.utils.*;
|
import me.zhengjie.utils.*;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
package me.zhengjie.modules.security.service;
|
package me.zhengjie.modules.security.service;
|
||||||
|
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import me.zhengjie.modules.security.config.bean.LoginProperties;
|
import me.zhengjie.modules.security.config.LoginProperties;
|
||||||
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
||||||
import me.zhengjie.utils.RedisUtils;
|
import me.zhengjie.utils.RedisUtils;
|
||||||
import me.zhengjie.utils.StringUtils;
|
import me.zhengjie.utils.StringUtils;
|
||||||
|
|
|
@ -18,14 +18,12 @@ package me.zhengjie.modules.security.service;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.zhengjie.exception.BadRequestException;
|
import me.zhengjie.exception.BadRequestException;
|
||||||
import me.zhengjie.exception.EntityNotFoundException;
|
|
||||||
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
||||||
import me.zhengjie.modules.system.service.DataService;
|
import me.zhengjie.modules.system.service.DataService;
|
||||||
import me.zhengjie.modules.system.service.RoleService;
|
import me.zhengjie.modules.system.service.RoleService;
|
||||||
import me.zhengjie.modules.system.service.UserService;
|
import me.zhengjie.modules.system.service.UserService;
|
||||||
import me.zhengjie.modules.system.service.dto.UserLoginDto;
|
import me.zhengjie.modules.system.service.dto.UserLoginDto;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,24 +43,14 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
||||||
public JwtUserDto loadUserByUsername(String username) {
|
public JwtUserDto loadUserByUsername(String username) {
|
||||||
JwtUserDto jwtUserDto = userCacheManager.getUserCache(username);
|
JwtUserDto jwtUserDto = userCacheManager.getUserCache(username);
|
||||||
if(jwtUserDto == null){
|
if(jwtUserDto == null){
|
||||||
UserLoginDto user;
|
UserLoginDto user = userService.getLoginData(username);
|
||||||
try {
|
|
||||||
user = userService.getLoginData(username);
|
|
||||||
} catch (EntityNotFoundException e) {
|
|
||||||
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
|
|
||||||
throw new UsernameNotFoundException(username, e);
|
|
||||||
}
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new UsernameNotFoundException("");
|
throw new BadRequestException("用户不存在");
|
||||||
} else {
|
} else {
|
||||||
if (!user.getEnabled()) {
|
if (!user.getEnabled()) {
|
||||||
throw new BadRequestException("账号未激活!");
|
throw new BadRequestException("账号未激活!");
|
||||||
}
|
}
|
||||||
jwtUserDto = new JwtUserDto(
|
jwtUserDto = new JwtUserDto(user, dataService.getDeptIds(user), roleService.buildAuthorities(user), user.getPassword());
|
||||||
user,
|
|
||||||
dataService.getDeptIds(user),
|
|
||||||
roleService.mapToGrantedAuthorities(user)
|
|
||||||
);
|
|
||||||
// 添加缓存数据
|
// 添加缓存数据
|
||||||
userCacheManager.addUserCache(username, jwtUserDto);
|
userCacheManager.addUserCache(username, jwtUserDto);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,10 @@
|
||||||
package me.zhengjie.modules.security.service.dto;
|
package me.zhengjie.modules.security.service.dto;
|
||||||
|
|
||||||
import com.alibaba.fastjson.annotation.JSONField;
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import me.zhengjie.modules.system.service.dto.UserLoginDto;
|
import me.zhengjie.modules.system.service.dto.UserLoginDto;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -38,6 +40,10 @@ public class JwtUserDto implements UserDetails {
|
||||||
|
|
||||||
private final List<AuthorityDto> authorities;
|
private final List<AuthorityDto> authorities;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@ApiModelProperty(value = "密码")
|
||||||
|
private String password;
|
||||||
|
|
||||||
public Set<String> getRoles() {
|
public Set<String> getRoles() {
|
||||||
return authorities.stream().map(AuthorityDto::getAuthority).collect(Collectors.toSet());
|
return authorities.stream().map(AuthorityDto::getAuthority).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,7 @@ public interface RoleService {
|
||||||
* @param user 用户信息
|
* @param user 用户信息
|
||||||
* @return 权限信息
|
* @return 权限信息
|
||||||
*/
|
*/
|
||||||
List<AuthorityDto> mapToGrantedAuthorities(UserDto user);
|
List<AuthorityDto> buildAuthorities(UserDto user);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证是否被用户关联
|
* 验证是否被用户关联
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package me.zhengjie.modules.system.service.dto;
|
package me.zhengjie.modules.system.service.dto;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Zheng Jie
|
* @author Zheng Jie
|
||||||
* @description 用户缓存时使用
|
* @description 用户缓存时使用
|
||||||
|
@ -22,6 +24,7 @@ package me.zhengjie.modules.system.service.dto;
|
||||||
**/
|
**/
|
||||||
public class UserLoginDto extends UserDto {
|
public class UserLoginDto extends UserDto {
|
||||||
|
|
||||||
|
@JSONField(serialize = false)
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
private Boolean isAdmin;
|
private Boolean isAdmin;
|
||||||
|
|
|
@ -164,7 +164,7 @@ public class RoleServiceImpl implements RoleService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Cacheable(key = "'auth:' + #p0.id")
|
@Cacheable(key = "'auth:' + #p0.id")
|
||||||
public List<AuthorityDto> mapToGrantedAuthorities(UserDto user) {
|
public List<AuthorityDto> buildAuthorities(UserDto user) {
|
||||||
Set<String> permissions = new HashSet<>();
|
Set<String> permissions = new HashSet<>();
|
||||||
// 如果是管理员直接返回
|
// 如果是管理员直接返回
|
||||||
if (user.getIsAdmin()) {
|
if (user.getIsAdmin()) {
|
||||||
|
|
|
@ -60,7 +60,7 @@ login:
|
||||||
# 存活时间/秒
|
# 存活时间/秒
|
||||||
idle-time: 21600
|
idle-time: 21600
|
||||||
# 验证码
|
# 验证码
|
||||||
login-code:
|
code:
|
||||||
# 验证码类型配置 查看 LoginProperties 类
|
# 验证码类型配置 查看 LoginProperties 类
|
||||||
code-type: arithmetic
|
code-type: arithmetic
|
||||||
# 登录图形验证码有效时间/分钟
|
# 登录图形验证码有效时间/分钟
|
||||||
|
|
|
@ -64,7 +64,7 @@ login:
|
||||||
# 存活时间/秒
|
# 存活时间/秒
|
||||||
idle-time: 21600
|
idle-time: 21600
|
||||||
# 验证码
|
# 验证码
|
||||||
login-code:
|
code:
|
||||||
# 验证码类型配置 查看 LoginProperties 类
|
# 验证码类型配置 查看 LoginProperties 类
|
||||||
code-type: arithmetic
|
code-type: arithmetic
|
||||||
# 登录图形验证码有效时间/分钟
|
# 登录图形验证码有效时间/分钟
|
||||||
|
|
Loading…
Reference in New Issue