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
|
||||
*/
|
||||
@Data
|
||||
class ApiError {
|
||||
public class ApiError {
|
||||
|
||||
private Integer status = 400;
|
||||
private Long timestamp;
|
||||
|
|
|
@ -15,18 +15,24 @@
|
|||
*/
|
||||
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.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhengjie.exception.BadRequestException;
|
||||
import me.zhengjie.utils.enums.DataScopeEnum;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
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.Objects;
|
||||
|
||||
/**
|
||||
* 获取当前登录的用户
|
||||
|
@ -34,8 +40,23 @@ import java.util.List;
|
|||
* @date 2019-01-17
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
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
|
||||
|
@ -45,34 +66,6 @@ public class SecurityUtils {
|
|||
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 /
|
||||
|
@ -91,9 +84,62 @@ public class SecurityUtils {
|
|||
*/
|
||||
public static String getDataScopeType() {
|
||||
List<Long> dataScopes = getCurrentUserDataScope();
|
||||
if(dataScopes.size() != 0){
|
||||
if(CollUtil.isEmpty(dataScopes)){
|
||||
return "";
|
||||
}
|
||||
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
|
||||
* 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.base.Captcha;
|
||||
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 org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 配置文件读取
|
||||
*
|
||||
* 登录验证码配置信息
|
||||
* @author liaojinlong
|
||||
* @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6
|
||||
* @date 2025-01-13
|
||||
*/
|
||||
@Data
|
||||
public class LoginProperties {
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "login.code")
|
||||
public class CaptchaConfig {
|
||||
|
||||
/**
|
||||
* 账号单用户 登录
|
||||
* 验证码配置
|
||||
*/
|
||||
private boolean singleLogin = false;
|
||||
|
||||
private LoginCode loginCode;
|
||||
|
||||
public static final String cacheKey = "user-login-cache:";
|
||||
|
||||
public boolean isSingleLogin() {
|
||||
return singleLogin;
|
||||
}
|
||||
@Getter
|
||||
private LoginCodeEnum codeType;
|
||||
|
||||
/**
|
||||
* 获取验证码生产类
|
||||
*
|
||||
* @return /
|
||||
* 验证码有效期 分钟
|
||||
*/
|
||||
public Captcha getCaptcha() {
|
||||
if (Objects.isNull(loginCode)) {
|
||||
loginCode = new LoginCode();
|
||||
if (Objects.isNull(loginCode.getCodeType())) {
|
||||
loginCode.setCodeType(LoginCodeEnum.ARITHMETIC);
|
||||
}
|
||||
}
|
||||
return switchCaptcha(loginCode);
|
||||
}
|
||||
private Long expiration = 5L;
|
||||
|
||||
/**
|
||||
* 验证码内容长度
|
||||
*/
|
||||
private int length = 4;
|
||||
|
||||
/**
|
||||
* 验证码宽度
|
||||
*/
|
||||
private int width = 111;
|
||||
|
||||
/**
|
||||
* 验证码高度
|
||||
*/
|
||||
private int height = 36;
|
||||
|
||||
/**
|
||||
* 验证码字体
|
||||
*/
|
||||
private String fontName;
|
||||
|
||||
/**
|
||||
* 字体大小
|
||||
*/
|
||||
private int fontSize = 25;
|
||||
|
||||
/**
|
||||
* 依据配置信息生产验证码
|
||||
*
|
||||
* @param loginCode 验证码配置信息
|
||||
* @return /
|
||||
*/
|
||||
private Captcha switchCaptcha(LoginCode loginCode) {
|
||||
public Captcha getCaptcha() {
|
||||
Captcha captcha;
|
||||
switch (loginCode.getCodeType()) {
|
||||
switch (codeType) {
|
||||
case ARITHMETIC:
|
||||
// 算术类型 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;
|
||||
case CHINESE:
|
||||
captcha = new ChineseCaptcha(loginCode.getWidth(), loginCode.getHeight());
|
||||
captcha.setLen(loginCode.getLength());
|
||||
captcha = new ChineseCaptcha(width, height);
|
||||
captcha.setLen(length);
|
||||
break;
|
||||
case CHINESE_GIF:
|
||||
captcha = new ChineseGifCaptcha(loginCode.getWidth(), loginCode.getHeight());
|
||||
captcha.setLen(loginCode.getLength());
|
||||
captcha = new ChineseGifCaptcha(width, height);
|
||||
captcha.setLen(length);
|
||||
break;
|
||||
case GIF:
|
||||
captcha = new GifCaptcha(loginCode.getWidth(), loginCode.getHeight());
|
||||
captcha.setLen(loginCode.getLength());
|
||||
captcha = new GifCaptcha(width, height);
|
||||
captcha.setLen(length);
|
||||
break;
|
||||
case SPEC:
|
||||
captcha = new SpecCaptcha(loginCode.getWidth(), loginCode.getHeight());
|
||||
captcha.setLen(loginCode.getLength());
|
||||
captcha = new SpecCaptcha(width, height);
|
||||
captcha.setLen(length);
|
||||
break;
|
||||
default:
|
||||
throw new BadConfigurationException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
|
||||
throw new BadRequestException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
|
||||
}
|
||||
if(StringUtils.isNotBlank(loginCode.getFontName())){
|
||||
captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN, loginCode.getFontSize()));
|
||||
if(StringUtils.isNotBlank(fontName)){
|
||||
captcha.setFont(new Font(fontName, Font.PLAIN, fontSize));
|
||||
}
|
||||
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
|
||||
* limitations under the License.
|
||||
*/
|
||||
package me.zhengjie.modules.security.config.bean;
|
||||
package me.zhengjie.modules.security.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Jwt参数配置
|
||||
|
@ -24,6 +26,8 @@ import lombok.Data;
|
|||
* @date 2019年11月28日
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "jwt")
|
||||
public class SecurityProperties {
|
||||
|
||||
/**
|
|
@ -16,7 +16,6 @@
|
|||
package me.zhengjie.modules.security.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
||||
import me.zhengjie.modules.security.security.*;
|
||||
import me.zhengjie.modules.security.service.OnlineUserService;
|
||||
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.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import java.util.*;
|
||||
|
||||
|
@ -54,7 +52,6 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
private final ApplicationContext applicationContext;
|
||||
private final SecurityProperties properties;
|
||||
private final OnlineUserService onlineUserService;
|
||||
private final UserCacheManager userCacheManager;
|
||||
|
||||
@Bean
|
||||
GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
||||
|
@ -75,7 +72,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
httpSecurity
|
||||
// 禁用 CSRF
|
||||
.csrf().disable()
|
||||
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.addFilter(corsFilter)
|
||||
// 授权异常
|
||||
.exceptionHandling()
|
||||
.authenticationEntryPoint(authenticationErrorHandler)
|
||||
|
@ -131,6 +128,6 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
}
|
||||
|
||||
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
|
||||
* 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.config.properties.RsaProperties;
|
||||
import me.zhengjie.exception.BadRequestException;
|
||||
import me.zhengjie.modules.security.config.bean.LoginCodeEnum;
|
||||
import me.zhengjie.modules.security.config.bean.LoginProperties;
|
||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
||||
import me.zhengjie.modules.security.config.CaptchaConfig;
|
||||
import me.zhengjie.modules.security.config.enums.LoginCodeEnum;
|
||||
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.service.UserDetailsServiceImpl;
|
||||
import me.zhengjie.modules.security.service.dto.AuthUserDto;
|
||||
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
||||
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.ResponseEntity;
|
||||
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.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -63,14 +64,15 @@ import java.util.concurrent.TimeUnit;
|
|||
@RequestMapping("/auth")
|
||||
@RequiredArgsConstructor
|
||||
@Api(tags = "系统:系统授权接口")
|
||||
public class AuthorizationController {
|
||||
public class AuthController {
|
||||
private final SecurityProperties properties;
|
||||
private final RedisUtils redisUtils;
|
||||
private final OnlineUserService onlineUserService;
|
||||
private final TokenProvider tokenProvider;
|
||||
private final AuthenticationManagerBuilder authenticationManagerBuilder;
|
||||
@Resource
|
||||
private LoginProperties loginProperties;
|
||||
private final LoginProperties loginProperties;
|
||||
private final CaptchaConfig captchaConfig;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final UserDetailsServiceImpl userDetailsService;
|
||||
|
||||
@Log("用户登录")
|
||||
@ApiOperation("登录授权")
|
||||
|
@ -88,27 +90,29 @@ public class AuthorizationController {
|
|||
if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
|
||||
throw new BadRequestException("验证码错误");
|
||||
}
|
||||
UsernamePasswordAuthenticationToken authenticationToken =
|
||||
new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
|
||||
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
|
||||
// 获取用户信息
|
||||
JwtUserDto jwtUser = userDetailsService.loadUserByUsername(authUser.getUsername());
|
||||
// 验证用户密码
|
||||
if (!passwordEncoder.matches(password, jwtUser.getPassword())) {
|
||||
throw new BadRequestException("登录密码错误");
|
||||
}
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities());
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
// 生成令牌与第三方系统获取令牌方式
|
||||
// UserDetails userDetails = userDetailsService.loadUserByUsername(userInfo.getUsername());
|
||||
// Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
// SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
String token = tokenProvider.createToken(authentication);
|
||||
final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
|
||||
// 生成令牌
|
||||
String token = tokenProvider.createToken(jwtUser);
|
||||
// 将密码设置为空
|
||||
jwtUser.setPassword(null);
|
||||
// 返回 token 与 用户信息
|
||||
Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
|
||||
put("token", properties.getTokenStartWith() + token);
|
||||
put("user", jwtUserDto);
|
||||
put("user", jwtUser);
|
||||
}};
|
||||
if (loginProperties.isSingleLogin()) {
|
||||
// 踢掉之前已经登录的token
|
||||
onlineUserService.kickOutForUsername(authUser.getUsername());
|
||||
}
|
||||
// 保存在线信息
|
||||
onlineUserService.save(jwtUserDto, token, request);
|
||||
onlineUserService.save(jwtUser, token, request);
|
||||
// 返回登录信息
|
||||
return ResponseEntity.ok(authInfo);
|
||||
}
|
||||
|
@ -116,14 +120,17 @@ public class AuthorizationController {
|
|||
@ApiOperation("获取用户信息")
|
||||
@GetMapping(value = "/info")
|
||||
public ResponseEntity<UserDetails> getUserInfo() {
|
||||
return ResponseEntity.ok(SecurityUtils.getCurrentUser());
|
||||
JwtUserDto jwtUser = (JwtUserDto) SecurityUtils.getCurrentUser();
|
||||
// 将密码设置为空
|
||||
jwtUser.setPassword(null);
|
||||
return ResponseEntity.ok(jwtUser);
|
||||
}
|
||||
|
||||
@ApiOperation("获取验证码")
|
||||
@AnonymousGetMapping(value = "/code")
|
||||
public ResponseEntity<Object> getCode() {
|
||||
// 获取运算的结果
|
||||
Captcha captcha = loginProperties.getCaptcha();
|
||||
Captcha captcha = captchaConfig.getCaptcha();
|
||||
String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
|
||||
//当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型
|
||||
String captchaValue = captcha.text();
|
||||
|
@ -131,7 +138,7 @@ public class AuthorizationController {
|
|||
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) {{
|
||||
put("img", captcha.toBase64());
|
|
@ -15,6 +15,9 @@
|
|||
*/
|
||||
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.web.access.AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -32,6 +35,10 @@ public class JwtAccessDeniedHandler implements AccessDeniedHandler {
|
|||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
|
||||
//当用户在没有授权的情况下访问受保护的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;
|
||||
|
||||
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.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
@ -26,14 +29,18 @@ import java.io.IOException;
|
|||
/**
|
||||
* @author Zheng Jie
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException {
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
|
||||
// 当用户尝试访问安全的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;
|
||||
|
||||
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.UserCacheManager;
|
||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
|
||||
|
@ -33,11 +33,10 @@ public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFi
|
|||
private final TokenProvider tokenProvider;
|
||||
private final SecurityProperties properties;
|
||||
private final OnlineUserService onlineUserService;
|
||||
private final UserCacheManager userCacheManager;
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,7 @@
|
|||
package me.zhengjie.modules.security.security;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
||||
import me.zhengjie.modules.security.service.UserCacheManager;
|
||||
import me.zhengjie.modules.security.config.SecurityProperties;
|
||||
import me.zhengjie.modules.security.service.dto.OnlineUserDto;
|
||||
import me.zhengjie.modules.security.service.OnlineUserService;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -33,7 +31,6 @@ import javax.servlet.ServletRequest;
|
|||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author /
|
||||
|
@ -45,19 +42,16 @@ public class TokenFilter extends GenericFilterBean {
|
|||
private final TokenProvider tokenProvider;
|
||||
private final SecurityProperties properties;
|
||||
private final OnlineUserService onlineUserService;
|
||||
private final UserCacheManager userCacheManager;
|
||||
|
||||
/**
|
||||
* @param tokenProvider Token
|
||||
* @param properties JWT
|
||||
* @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.onlineUserService = onlineUserService;
|
||||
this.tokenProvider = tokenProvider;
|
||||
this.userCacheManager = userCacheManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -66,25 +60,17 @@ public class TokenFilter extends GenericFilterBean {
|
|||
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
|
||||
String token = resolveToken(httpServletRequest);
|
||||
// 对于 Token 为空的不需要去查 Redis
|
||||
if (StrUtil.isNotBlank(token)) {
|
||||
OnlineUserDto onlineUserDto = null;
|
||||
boolean cleanUserCache = false;
|
||||
try {
|
||||
String loginKey = tokenProvider.loginKey(token);
|
||||
onlineUserDto = onlineUserService.getOne(loginKey);
|
||||
} catch (ExpiredJwtException e) {
|
||||
log.error(e.getMessage());
|
||||
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)) {
|
||||
if(StrUtil.isNotBlank(token)){
|
||||
// 获取用户Token的Key
|
||||
String loginKey = tokenProvider.loginKey(token);
|
||||
OnlineUserDto onlineUserDto = onlineUserService.getOne(loginKey);
|
||||
// 判断用户在线信息是否为空
|
||||
if (onlineUserDto != null) {
|
||||
// Token 续期判断
|
||||
tokenProvider.checkRenewal(token);
|
||||
// 获取认证信息,设置上下文
|
||||
Authentication authentication = tokenProvider.getAuthentication(token);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
// Token 续期
|
||||
tokenProvider.checkRenewal(token);
|
||||
}
|
||||
}
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
|
|
|
@ -23,7 +23,8 @@ import io.jsonwebtoken.*;
|
|||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
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 org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
|
@ -42,11 +43,12 @@ import java.util.concurrent.TimeUnit;
|
|||
@Component
|
||||
public class TokenProvider implements InitializingBean {
|
||||
|
||||
private final SecurityProperties properties;
|
||||
private final RedisUtils redisUtils;
|
||||
public static final String AUTHORITIES_KEY = "user";
|
||||
private JwtParser jwtParser;
|
||||
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) {
|
||||
this.properties = properties;
|
||||
|
@ -68,15 +70,19 @@ public class TokenProvider implements InitializingBean {
|
|||
* 创建Token 设置永不过期,
|
||||
* Token 的时间有效性转到Redis 维护
|
||||
*
|
||||
* @param authentication /
|
||||
* @param user /
|
||||
* @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
|
||||
// 加入ID确保生成的 Token 都不一致
|
||||
.setId(IdUtil.simpleUUID())
|
||||
.claim(AUTHORITIES_KEY, authentication.getName())
|
||||
.setSubject(authentication.getName())
|
||||
.setClaims(claims)
|
||||
.setSubject(user.getUsername())
|
||||
.compact();
|
||||
}
|
||||
|
||||
|
|
|
@ -19,12 +19,11 @@ import lombok.AllArgsConstructor;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhengjie.modules.security.security.TokenProvider;
|
||||
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.OnlineUserDto;
|
||||
import me.zhengjie.utils.*;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
package me.zhengjie.modules.security.service;
|
||||
|
||||
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.utils.RedisUtils;
|
||||
import me.zhengjie.utils.StringUtils;
|
||||
|
|
|
@ -18,14 +18,12 @@ package me.zhengjie.modules.security.service;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhengjie.exception.BadRequestException;
|
||||
import me.zhengjie.exception.EntityNotFoundException;
|
||||
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
||||
import me.zhengjie.modules.system.service.DataService;
|
||||
import me.zhengjie.modules.system.service.RoleService;
|
||||
import me.zhengjie.modules.system.service.UserService;
|
||||
import me.zhengjie.modules.system.service.dto.UserLoginDto;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
|
@ -45,24 +43,14 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
|||
public JwtUserDto loadUserByUsername(String username) {
|
||||
JwtUserDto jwtUserDto = userCacheManager.getUserCache(username);
|
||||
if(jwtUserDto == null){
|
||||
UserLoginDto user;
|
||||
try {
|
||||
user = userService.getLoginData(username);
|
||||
} catch (EntityNotFoundException e) {
|
||||
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
|
||||
throw new UsernameNotFoundException(username, e);
|
||||
}
|
||||
UserLoginDto user = userService.getLoginData(username);
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException("");
|
||||
throw new BadRequestException("用户不存在");
|
||||
} else {
|
||||
if (!user.getEnabled()) {
|
||||
throw new BadRequestException("账号未激活!");
|
||||
}
|
||||
jwtUserDto = new JwtUserDto(
|
||||
user,
|
||||
dataService.getDeptIds(user),
|
||||
roleService.mapToGrantedAuthorities(user)
|
||||
);
|
||||
jwtUserDto = new JwtUserDto(user, dataService.getDeptIds(user), roleService.buildAuthorities(user), user.getPassword());
|
||||
// 添加缓存数据
|
||||
userCacheManager.addUserCache(username, jwtUserDto);
|
||||
}
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
package me.zhengjie.modules.security.service.dto;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import me.zhengjie.modules.system.service.dto.UserLoginDto;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import java.util.List;
|
||||
|
@ -38,6 +40,10 @@ public class JwtUserDto implements UserDetails {
|
|||
|
||||
private final List<AuthorityDto> authorities;
|
||||
|
||||
@Setter
|
||||
@ApiModelProperty(value = "密码")
|
||||
private String password;
|
||||
|
||||
public Set<String> getRoles() {
|
||||
return authorities.stream().map(AuthorityDto::getAuthority).collect(Collectors.toSet());
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ public interface RoleService {
|
|||
* @param user 用户信息
|
||||
* @return 权限信息
|
||||
*/
|
||||
List<AuthorityDto> mapToGrantedAuthorities(UserDto user);
|
||||
List<AuthorityDto> buildAuthorities(UserDto user);
|
||||
|
||||
/**
|
||||
* 验证是否被用户关联
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package me.zhengjie.modules.system.service.dto;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
|
||||
/**
|
||||
* @author Zheng Jie
|
||||
* @description 用户缓存时使用
|
||||
|
@ -22,6 +24,7 @@ package me.zhengjie.modules.system.service.dto;
|
|||
**/
|
||||
public class UserLoginDto extends UserDto {
|
||||
|
||||
@JSONField(serialize = false)
|
||||
private String password;
|
||||
|
||||
private Boolean isAdmin;
|
||||
|
|
|
@ -164,7 +164,7 @@ public class RoleServiceImpl implements RoleService {
|
|||
|
||||
@Override
|
||||
@Cacheable(key = "'auth:' + #p0.id")
|
||||
public List<AuthorityDto> mapToGrantedAuthorities(UserDto user) {
|
||||
public List<AuthorityDto> buildAuthorities(UserDto user) {
|
||||
Set<String> permissions = new HashSet<>();
|
||||
// 如果是管理员直接返回
|
||||
if (user.getIsAdmin()) {
|
||||
|
|
|
@ -60,7 +60,7 @@ login:
|
|||
# 存活时间/秒
|
||||
idle-time: 21600
|
||||
# 验证码
|
||||
login-code:
|
||||
code:
|
||||
# 验证码类型配置 查看 LoginProperties 类
|
||||
code-type: arithmetic
|
||||
# 登录图形验证码有效时间/分钟
|
||||
|
|
|
@ -64,7 +64,7 @@ login:
|
|||
# 存活时间/秒
|
||||
idle-time: 21600
|
||||
# 验证码
|
||||
login-code:
|
||||
code:
|
||||
# 验证码类型配置 查看 LoginProperties 类
|
||||
code-type: arithmetic
|
||||
# 登录图形验证码有效时间/分钟
|
||||
|
|
Loading…
Reference in New Issue