PoffyZhang 1 year ago
parent
commit
893aae9bff
23 changed files with 1132 additions and 8 deletions
  1. +8
    -8
      ningda-generator/src/main/java/com/ningdatech/generator/config/GeneratorCodeConfig.java
  2. +98
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/common/constants/BizConst.java
  3. +17
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/common/errorcode/AppErrorCode.java
  4. +62
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/common/handler/GlobalExceptionHandler.java
  5. +56
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/common/handler/GlobalResponseHandler.java
  6. +12
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/manage/UserAuthLoginManage.java
  7. +97
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/WebSecurityConfig.java
  8. +64
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/AuthProperties.java
  9. +28
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/AuthenticationBeanConfig.java
  10. +49
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/RedisSessionConfig.java
  11. +47
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/constants/AuthTypeEnum.java
  12. +12
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/constants/SessionTimeConstant.java
  13. +34
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/errorcode/AuthErrorCodeEnum.java
  14. +41
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultExpiredSessionStrategy.java
  15. +47
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginFailureHandler.java
  16. +35
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginSuccessHandler.java
  17. +28
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLogoutSuccessHandler.java
  18. +81
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/model/UserInfoDetails.java
  19. +41
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/PasswordLoginUserDetailService.java
  20. +71
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthFilter.java
  21. +64
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthProvider.java
  22. +64
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthSecurityConfig.java
  23. +76
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthToken.java

+ 8
- 8
ningda-generator/src/main/java/com/ningdatech/generator/config/GeneratorCodeConfig.java View File

@@ -8,14 +8,14 @@ import java.util.Collections;

/**
* @description: 自动生成code代码
* @author: liushuai
* @date: 2022/3/25 14:20
* @author: liuxinxin
* @date: 2023/01/03 09:20
*/
public class GeneratorCodeConfig {

private static final String PATH_LXX = "/Users/liuxinxin/IdeaProjects/car_rental/ningda-car-rental-api/src/main/java";
private static final String PATH_YYD = "/Users/wendy/code project/java/car_rental/ningda-car-rental-api/src/main/java";
private static final String PATH_LS = "/Users/qinxianyun/Documents/qin/ningdatech/car_rental/ningda-car-rental-api/src/main/java";
private static final String PATH_LXX = "";
private static final String PATH_YYD = "";
private static final String PATH_LS = "";
private static final String PATH_ZPF = "";
private static final String PATH_CMM = "";

@@ -41,11 +41,11 @@ public class GeneratorCodeConfig {
// 设置父包名
builder.parent("com.ningdatech")
// 设置父包模块名
.moduleName("rentalcar." + packageName)
.moduleName("pmapi." + packageName)
// 设置mapperXml生成路径
.pathInfo(Collections.singletonMap(OutputFile.mapperXml,
//设置自己的生成路径
path + "/com/ningdatech/rentalcar/" + packageName + "/mapper"));
path + "/com/ningdatech/pmapi/" + packageName + "/mapper"));
})
.strategyConfig(builder -> {
builder.addTablePrefix("nd");
@@ -58,7 +58,7 @@ public class GeneratorCodeConfig {
}

public static void main(String[] args) {
generate("Liuxinxin", "order", PATH_YYD, "nd_order_status_change");
generate("Liuxinxin", "null", PATH_LXX, "null");
}

}

+ 98
- 0
pmapi/src/main/java/com/ningdatech/pmapi/common/constants/BizConst.java View File

@@ -0,0 +1,98 @@
package com.ningdatech.pmapi.common.constants;

import com.ningdatech.basic.model.ApiResponse;

import java.math.BigDecimal;

/**
* <p>
* 业务常量
* </p>
*
* @author WendyYang
* @since 13:42 2022/12/1
*/
public interface BizConst {

/**
* SQL查询一条
*/
String LIMIT_1 = "limit 1";

String COOKIE_KEY = "ND_CAR_RENTAL_JSESSION";

/**
* 一小时秒数
**/
BigDecimal SECONDS_BY_HOUR = new BigDecimal(60 * 60);

/**
* 十分钟的毫秒数
*/
long MILLS_10_MIN = 1000L * 60 * 10;

/**
* 中国行政区划编码
*/
long ROOT_REGION_CODE = 100000L;

/**
* 一级行政区划数量
*/
int NUM_PROVINCE = 34;

/**
* 默认的父id
*/
long PARENT_ID = 0L;

/**
* 默认树层级
*/
int TREE_GRADE = 0;

/**
* 默认的排序
*/
int SORT_VALUE = 0;

/**
* 浙江省的region_id
*/
long ZJ_REGION_CODE = 330000L;

/**
* 省/直辖市 level
*/
int GOV_L1 = 1;

/**
* 市 level
*/
int GOV_L2 = 2;

/**
* 区/县 level
*/
int GOV_L3 = 3;

/**
* 密码正则:长度8-20位且至少包含大写字母、小写字母、数字或特殊符号中的任意三种
*/
String REGEX_PASS = "^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\\W_]+$)(?![a-z0-9]+$)(?![a-z\\W_]+$)(?![0-9\\W_]+$)[a-zA-Z0-9\\W_]{8,20}$";

/**
* 租车费率
*/
BigDecimal RATE_CAR_RENTAL = new BigDecimal("1.13");

/**
* 服务费率
*/
BigDecimal RATE_SERVICE = new BigDecimal("0.0442");

ApiResponse<Void> UNAUTHENTICATED = ApiResponse.of(401, "用户未登录", null);

int MAX_EXPORT_COUNT = 5000;

}

+ 17
- 0
pmapi/src/main/java/com/ningdatech/pmapi/common/errorcode/AppErrorCode.java View File

@@ -0,0 +1,17 @@
package com.ningdatech.pmapi.common.errorcode;

public enum AppErrorCode {
USER(100),

AUTH(101);

private final Integer code;

AppErrorCode(Integer code) {
this.code = code;
}

public Integer getCode() {
return code;
}
}

+ 62
- 0
pmapi/src/main/java/com/ningdatech/pmapi/common/handler/GlobalExceptionHandler.java View File

@@ -0,0 +1,62 @@
package com.ningdatech.pmapi.common.handler;

import com.ningdatech.basic.enumeration.Status;
import com.ningdatech.basic.model.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;

/**
* @description: 统一错误处理
* @author: liuxinxin
* @date: 2023/01/03 11:39
*/
@Slf4j
@ControllerAdvice
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class GlobalExceptionHandler {

@ResponseBody
@ExceptionHandler(value = NoHandlerFoundException.class)
public ApiResponse<Void> noHandlerFoundException(NoHandlerFoundException e) {
log.error("【全局异常拦截】NoHandlerFoundException: 请求方法 {}, 请求路径 {}", e.getRequestURL(), e.getHttpMethod());
return ApiResponse.ofStatus(Status.REQUEST_NOT_FOUND);
}

@ResponseBody
@ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})
public ApiResponse<Void> bindException(BindException e) {
String msg = e.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(","));
return ApiResponse.of(Status.BAD_REQUEST.getCode(), msg, null);
}

@ResponseBody
@ExceptionHandler(value = ConstraintViolationException.class)
public ApiResponse<Void> constraintViolationException(ConstraintViolationException e) {
String msg = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(","));
return ApiResponse.of(Status.BAD_REQUEST.getCode(), msg, null);
}

@ResponseBody
@ExceptionHandler(value = Exception.class)
public ApiResponse<Void> handlerException(Exception e) {
log.error("【全局异常拦截】: 异常信息 {}: {} ", e.getClass().getSimpleName(), e);
return ApiResponse.of(Status.BAD_REQUEST.getCode(), e.getMessage(), null);
}

}

+ 56
- 0
pmapi/src/main/java/com/ningdatech/pmapi/common/handler/GlobalResponseHandler.java View File

@@ -0,0 +1,56 @@
package com.ningdatech.pmapi.common.handler;

import cn.hutool.json.JSONUtil;
import com.ningdatech.basic.model.ApiResponse;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
* @Author liuxinxin
* @Date 2021/7/21 11:26
* @Version 1.0
**/
@RestControllerAdvice(basePackages = {})
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {

private static final String SWAGGER_CLASS_PREFIX = "springfox.documentation";

@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return filter(methodParameter);
}

@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
ApiResponse<Object> apiResponse = ApiResponse.ofSuccess(o);
// 处理字符串时,遇到了类型转换的问题,debug一步一步跟踪,原来是对于字符串的ContentType是“text-plain”,
// ConverterType是StringHttpMessageConverter这个类型转换,
// 由于将结果封装成了自定义的ApiResponse类型,所以有ApiResponse转换成String报错
// 所以需要对String类型的返回值单独进行处理
if (o instanceof String) {
return JSONUtil.toJsonStr(apiResponse);
}
return ApiResponse.ofSuccess(o);
}

private Boolean filter(MethodParameter methodParameter) {
Class<?> declaringClass = methodParameter.getDeclaringClass();
// swagger中的所有返回不进行统一封装
if (declaringClass.getName().contains(SWAGGER_CLASS_PREFIX)) {
return false;
}
if (methodParameter.hasMethodAnnotation(ExceptionHandler.class)) {
return false;
}
// 如果本身就是使用ApiResponse返回,则不需要进行格式化
return !methodParameter.getParameterType().equals(ApiResponse.class);
}
}

+ 12
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/manage/UserAuthLoginManage.java View File

@@ -0,0 +1,12 @@
package com.ningdatech.pmapi.user.manage;

import org.springframework.stereotype.Component;

/**
* @author liuxinxin
* @date 2023/1/3 上午10:57
*/

@Component
public class UserAuthLoginManage {
}

+ 97
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/WebSecurityConfig.java View File

@@ -0,0 +1,97 @@
package com.ningdatech.pmapi.user.security.auth;

import com.ningdatech.basic.util.NdJsonUtil;
import com.ningdatech.basic.util.StrPool;
import com.ningdatech.pmapi.common.constants.BizConst;
import com.ningdatech.pmapi.user.security.auth.config.AuthProperties;
import com.ningdatech.pmapi.user.security.auth.handler.DefaultExpiredSessionStrategy;
import com.ningdatech.pmapi.user.security.auth.password.UsernamePasswordAuthSecurityConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

import java.io.PrintWriter;
import java.util.Map;
import java.util.Set;

/**
* @Author LiuXinXin
* @Date 2020/7/28 4:14 下午
* @Version 1.0
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

private final AuthProperties authProperties;
private final UsernamePasswordAuthSecurityConfig usernamePasswordAuthSecurityConfig;
private final LogoutSuccessHandler logoutSuccessHandler;
private final DefaultExpiredSessionStrategy defaultExpiredSessionStrategy;

public WebSecurityConfig(AuthProperties authProperties,
UsernamePasswordAuthSecurityConfig usernamePasswordAuthSecurityConfig,
@Qualifier(value = "defaultLogoutSuccessHandler") LogoutSuccessHandler logoutSuccessHandler,
DefaultExpiredSessionStrategy defaultExpiredSessionStrategy) {
this.authProperties = authProperties;
this.usernamePasswordAuthSecurityConfig = usernamePasswordAuthSecurityConfig;
this.logoutSuccessHandler = logoutSuccessHandler;
this.defaultExpiredSessionStrategy = defaultExpiredSessionStrategy;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
assemblerPreAuthUrls(http);
http.formLogin()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint())
.and().apply(usernamePasswordAuthSecurityConfig)
.and()
.authorizeRequests()
.antMatchers(authProperties.getIgnoreAuthUrlsArray()).permitAll()
.anyRequest()
.authenticated()
.and()
// 防止固定会话攻击,Spring security的默认配置就是如此:
// 登陆成功之后会创建一个新的会话,然后将旧的session信息复制到新的session中(客户端的sessionId变了)
.sessionManagement().invalidSessionUrl(authProperties.getInvalidSessionUrl()).sessionFixation()
.migrateSession()
// .invalidSessionStrategy(defaultInvalidSessionStrategy)
.maximumSessions(10).maxSessionsPreventsLogin(true).expiredSessionStrategy(defaultExpiredSessionStrategy)
.and().and().logout().logoutUrl(authProperties.getLogoutUrl()).logoutSuccessHandler(logoutSuccessHandler)
.deleteCookies(BizConst.COOKIE_KEY)
.and()
// 开启csrf验证,需要前端同步传入token
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers(authProperties.getIgnoreCsrfUrlsArray());


}

private AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, authException) -> {
response.setContentType(StrPool.CONTENT_TYPE);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
PrintWriter writer = response.getWriter();
writer.write(NdJsonUtil.getInstance().writeValueAsString(BizConst.UNAUTHENTICATED));
writer.flush();
writer.close();
};
}

private void assemblerPreAuthUrls(HttpSecurity http) throws Exception {
Map<String, String[]> roleArrayMap = authProperties.getRoleArrayMap();
Set<String> roleSet = roleArrayMap.keySet();
for (String role : roleSet) {
String[] urlsArray = roleArrayMap.get(role);
if (urlsArray != null && urlsArray.length > 0) {
http.authorizeRequests().antMatchers(urlsArray).hasAuthority(role);
}
}
}

}

+ 64
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/AuthProperties.java View File

@@ -0,0 +1,64 @@
package com.ningdatech.pmapi.user.security.auth.config;

import cn.hutool.core.collection.CollectionUtil;
import com.ningdatech.basic.factory.PropertySourceFactory;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.util.*;

/**
* @Author LiuXinXin
* @Date 2020/8/1 6:06 下午
* @Version 1.0
**/
@Configuration
@PropertySource(value = "classpath:security/auth-${spring.profiles.active}.yml"
, encoding = "utf-8", factory = PropertySourceFactory.class)
@ConfigurationProperties(prefix = "security.auth")
@Component
@Data
public class AuthProperties {

private String authRequireUrl;

private String invalidSessionUrl;

private String passwordLoginUrl;

private String logoutUrl;

private List<String> ignoreAuthUrls;

private List<String> ignoreCsrfUrls;

private Map<String, List<String>> roleMap = new HashMap<>();

public String[] getIgnoreAuthUrlsArray() {
String[] stringArray = new String[ignoreAuthUrls.size()];
return ignoreAuthUrls.toArray(stringArray);
}

public String[] getIgnoreCsrfUrlsArray() {
String[] stringArray = new String[ignoreCsrfUrls.size()];
return ignoreCsrfUrls.toArray(stringArray);
}

public Map<String, String[]> getRoleArrayMap() {
Map<String, String[]> roleArrayMap = new HashMap<>();
if (Objects.nonNull(roleMap)) {
Set<String> keySet = roleMap.keySet();
for (String key : keySet) {
List<String> urls = roleMap.get(key);
if (CollectionUtil.isNotEmpty(urls)) {
String[] stringArray = new String[urls.size()];
roleArrayMap.put(key, urls.toArray(stringArray));
}
}
}
return roleArrayMap;
}
}

+ 28
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/AuthenticationBeanConfig.java View File

@@ -0,0 +1,28 @@
package com.ningdatech.pmapi.user.security.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
* @Author LiuXinXin
* @Date 2020/7/28 1:00 下午
* @Version 1.0
**/
@Configuration
public class AuthenticationBeanConfig {

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}

}

+ 49
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/RedisSessionConfig.java View File

@@ -0,0 +1,49 @@
//package com.ningdatech.pmapi.user.security.auth.config;
//
//import com.ningdatech.basic.util.StrPool;
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//
///**
// * <p>
// * 设置session的过期时间为一天
// * </p>
// *
// * @Author LiuXinXin
// * @Date 2020/7/29 9:46 上午
// * @Version 1.0
// **/
//@Configuration
//@EnableRedisHttpSession(
// maxInactiveIntervalInSeconds = RedisSessionConfig.SESSION_TIMEOUT,
// redisNamespace = RedisSessionConfig.REDIS_NAMESPACE
//)
//public class RedisSessionConfig {
//
// static final int SESSION_TIMEOUT = 24 * 60 * 60 * 10;
//
// static final String REDIS_NAMESPACE = "#{redisSessionConfig.getRedisNamespace()}";
//
// @Value("${nd.cache.def.keyPrefix:}")
// private String keyPrefix;
//
// public String getRedisNamespace() {
// return (StrUtils.isBlank(keyPrefix) ? StrPool.EMPTY : keyPrefix + StrPool.COLON) + RedisIndexedSessionRepository.DEFAULT_NAMESPACE;
// }
//
// @Bean
// public CookieHttpSessionIdResolver sessionIdResolver() {
// // 创建 CookieHttpSessionIdResolver 对象
// CookieHttpSessionIdResolver sessionIdResolver = new CookieHttpSessionIdResolver();
// // 创建 DefaultCookieSerializer 对象
// DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
//
// // 设置到 sessionIdResolver 中
// sessionIdResolver.setCookieSerializer(cookieSerializer);
// cookieSerializer.setCookieName(BizConst.COOKIE_KEY);
// cookieSerializer.setCookieMaxAge(SessionTimeConstant.SESSION_TIME_SECONDS);
// return sessionIdResolver;
// }
//
//}

+ 47
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/constants/AuthTypeEnum.java View File

@@ -0,0 +1,47 @@
package com.ningdatech.pmapi.user.security.auth.constants;

/**
* @author Liuxinxin
* @date 2021/7/30 下午2:10
*/

public enum AuthTypeEnum {

/**
* 手机 + 密码的认证方式
*/
PHONE_PASSWORD("phone_password"),

/**
* 子账号 账号 + 密码的认证方式
*/
ACCOUNT_PASSWORD("account_password");

private final String key;

AuthTypeEnum(String key) {
this.key = key;
}

public static boolean contains(String key) {
for (AuthTypeEnum value : AuthTypeEnum.values()) {
if (key.equals(value.getKey())) {
return true;
}
}
return false;
}

public String getKey() {
return key;
}

public static AuthTypeEnum of(String key) {
for (AuthTypeEnum value : AuthTypeEnum.values()) {
if (key.equals(value.getKey())) {
return value;
}
}
throw new RuntimeException(String.format("invalid AuthTypeEnum = %s", key));
}
}

+ 12
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/constants/SessionTimeConstant.java View File

@@ -0,0 +1,12 @@
package com.ningdatech.pmapi.user.security.auth.constants;

/**
* @Author LiuXinXin
* @Date 2022/2/17 12:59 上午
* @Version 1.0
**/
public class SessionTimeConstant {

public static final Integer SESSION_TIME_SECONDS = 24 * 60 * 60 * 10;

}

+ 34
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/errorcode/AuthErrorCodeEnum.java View File

@@ -0,0 +1,34 @@
package com.ningdatech.pmapi.user.security.auth.errorcode;


import com.ningdatech.pmapi.common.errorcode.AppErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
* @author LiuXinXin
* @date 2021/7/30 上午10:59
*/

@AllArgsConstructor
@Getter
public enum AuthErrorCodeEnum {

USERNAME_OR_PASSWORD_ERROR(AppErrorCode.AUTH.getCode() + "100000"
, "登陆时用户名或者密码错误"),

ACCOUNT_ALREADY_EXIST_WHEN_REGISTER(AppErrorCode.AUTH.getCode() + "100001"
, "账号已经存在"),

SESSION_EXPIRED(AppErrorCode.AUTH.getCode() + "100002"
, "用户session过期,登陆状态失效");


private final String code;

private final String msg;

public Integer getCode() {
return Integer.valueOf(code);
}
}

+ 41
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultExpiredSessionStrategy.java View File

@@ -0,0 +1,41 @@
package com.ningdatech.pmapi.user.security.auth.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ningdatech.basic.model.ApiResponse;
import com.ningdatech.pmapi.user.security.auth.errorcode.AuthErrorCodeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Author LiuXinXin
* @Date 2020/8/20 11:15 上午
* @Version 1.0
**/
@Component
public class DefaultExpiredSessionStrategy implements SessionInformationExpiredStrategy {

public static final Logger LOG = LoggerFactory.getLogger(DefaultExpiredSessionStrategy.class);

@Autowired
private ObjectMapper objectMapper;

@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent sessionInformationExpiredEvent)
throws IOException, ServletException {
if (LOG.isInfoEnabled()) {
LOG.info("session is expired");
}
HttpServletResponse response = sessionInformationExpiredEvent.getResponse();
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(
ApiResponse.of(AuthErrorCodeEnum.SESSION_EXPIRED.getCode(), AuthErrorCodeEnum.SESSION_EXPIRED.getMsg(), null)));
}
}

+ 47
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginFailureHandler.java View File

@@ -0,0 +1,47 @@
package com.ningdatech.pmapi.user.security.auth.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ningdatech.basic.model.ApiResponse;
import com.ningdatech.pmapi.user.security.auth.errorcode.AuthErrorCodeEnum;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Author LiuXinXin
* @Date 2020/8/3 8:32 下午
* @Version 1.0
**/
@Component("defaultLoginFailureHandler")
public class DefaultLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {

private final ObjectMapper objectMapper = new ObjectMapper();

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
int errorCode;
String errorMsg;
// 所有的认证异常都可以在这里添加,目前只支持用户名密码错误异常

if (exception instanceof BadCredentialsException || exception instanceof UsernameNotFoundException) {
errorCode = AuthErrorCodeEnum.USERNAME_OR_PASSWORD_ERROR.getCode();
errorMsg = exception.getMessage();
} else {
errorCode = ApiResponse.ERROR_CODE;
errorMsg = ApiResponse.ERROR_MSG;
}
response.setStatus(400);
response.getWriter()
.write(objectMapper.writeValueAsString(ApiResponse.of(errorCode, errorMsg, null)));
}

}

+ 35
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginSuccessHandler.java View File

@@ -0,0 +1,35 @@
package com.ningdatech.pmapi.user.security.auth.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ningdatech.basic.model.ApiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Author LiuXinXin
* @Date 2020/8/3 8:32 下午
* @Version 1.0
**/
@Component("defaultLoginSuccessHandler")
@Primary
public class DefaultLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

@Autowired
private ObjectMapper objectMapper;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write(objectMapper.writeValueAsString(ApiResponse.ofSuccess()));
}

}

+ 28
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLogoutSuccessHandler.java View File

@@ -0,0 +1,28 @@
package com.ningdatech.pmapi.user.security.auth.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ningdatech.basic.model.ApiResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* 默认的退出成功处理器
*/
@Component("defaultLogoutSuccessHandler")
public class DefaultLogoutSuccessHandler implements LogoutSuccessHandler {

private final ObjectMapper objectMapper = new ObjectMapper();

@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException {
// 退出成功后返回 和前端约定的Json
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(ApiResponse.ofSuccess()));
}
}

+ 81
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/model/UserInfoDetails.java View File

@@ -0,0 +1,81 @@
package com.ningdatech.pmapi.user.security.auth.model;

import cn.hutool.core.collection.CollectionUtil;
import com.ningdatech.basic.auth.AbstractLoginUser;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

/**
* @author LiuXinXin
* @date 2022/8/1 下午3:32
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class UserInfoDetails extends AbstractLoginUser implements UserDetails {

private String realName;

private String password;

private String role;

/**
* 区域code
*/
private Long regionCode;

private Long companyId;

/**
* 所负责公司id列表
*/
private List<Long> responsibleCompanyIdList;

/**
* 获取用户权限
*
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(this.role);
return CollectionUtil.toList(simpleGrantedAuthority);
}

@Override
public String getPassword() {
return password;
}

@Override
public String getUsername() {
return getIdentifier();
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

}

+ 41
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/PasswordLoginUserDetailService.java View File

@@ -0,0 +1,41 @@
package com.ningdatech.pmapi.user.security.auth.password;


import com.ningdatech.pmapi.user.manage.UserAuthLoginManage;
import com.ningdatech.pmapi.user.security.auth.model.UserInfoDetails;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
* @author LiuXinXin
* @date 2022/9/30 上午9:49
*/

@Service("passwordLoginUserDetailService")
@RequiredArgsConstructor
public class PasswordLoginUserDetailService implements UserDetailsService {

private final UserAuthLoginManage userAuthLoginManage;

@Override
public UserInfoDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// UserFullInfoDTO userFullInfoDTO = userAuthLoginFacade.queryUserInfoInPasswordAuth(username);
// if (Objects.isNull(userFullInfoDTO)) {
// throw new UsernameNotFoundException(String.format("%s user not exist", username));
// }
// UserInfoDetails userInfoDetails = new UserInfoDetails();
// userInfoDetails.setUserId(userFullInfoDTO.getUserId());
// userInfoDetails.setUsername(userFullInfoDTO.getUsername());
// userInfoDetails.setRealName(userFullInfoDTO.getRealName());
// userInfoDetails.setRole(userFullInfoDTO.getRole());
// userInfoDetails.setRegionCode(userFullInfoDTO.getRegionCode());
// userInfoDetails.setCompanyId(userFullInfoDTO.getCompanyId());
// userInfoDetails.setResponsibleCompanyIdList(userFullInfoDTO.getResponsibleCompanyIdList());
// userInfoDetails.setIdentifier(userFullInfoDTO.getIdentifier());
// userInfoDetails.setPassword(userFullInfoDTO.getCredential());
// return userInfoDetails;
return null;
}
}

+ 71
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthFilter.java View File

@@ -0,0 +1,71 @@
package com.ningdatech.pmapi.user.security.auth.password;

import com.ningdatech.basic.exception.BizException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @Author LiuXinXin
* @Date 2020/8/3 8:46 下午
* @Version 1.0
**/
public class UsernamePasswordAuthFilter extends AbstractAuthenticationProcessingFilter {

private boolean postOnly = true;

private static final String USERNAME_PARAMETER = "username";
private static final String PASSWORD_PARAMETER = "password";


// ~ Constructors
// ===================================================================================================

public UsernamePasswordAuthFilter(String processingUrl) {
super(new AntPathRequestMatcher(processingUrl, HttpMethod.POST.name()));
}

// ~ Methods
// ========================================================================================================

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
throw new AuthenticationServiceException("请求方法错误");
}
String username = request.getParameter(USERNAME_PARAMETER);
String password = request.getParameter(PASSWORD_PARAMETER);
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
throw new UsernameNotFoundException("用户名或密码不能为空");
}
username = username.trim();
password = password.trim();
try {
UsernamePasswordAuthToken authRequest = new UsernamePasswordAuthToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
} catch (AuthenticationException e) {
throw new BadCredentialsException("账号或密码错误");
} catch (BizException e) {
throw new BadCredentialsException(e.getMessage());
} catch (Exception e) {
throw new InternalAuthenticationServiceException("授权失败:", e);
}
}

protected void setDetails(HttpServletRequest request, UsernamePasswordAuthToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}

+ 64
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthProvider.java View File

@@ -0,0 +1,64 @@
package com.ningdatech.pmapi.user.security.auth.password;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
* @Author LiuXinXin
* @Date 2020/8/3 8:55 下午
* @Version 1.0
**/
public class UsernamePasswordAuthProvider implements AuthenticationProvider {

private UserDetailsService userDetailsService;

private PasswordEncoder passwordEncoder;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof UsernamePasswordAuthToken)) {
throw new RuntimeException("CustomAuthProvider 只支持 CustomAuthToken");
}
UsernamePasswordAuthToken authenticationToken = (UsernamePasswordAuthToken) authentication;

UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
if (user == null) {
throw new InternalAuthenticationServiceException("can not get user info!");
}
additionalAuthenticationChecks(user, authenticationToken);
// 校验用户是否有当前端的登陆权限
// 将用户定义的user放入token中,这样可以在session中查询到所有自定义的用户信息
return new UsernamePasswordAuthToken(user, user.getPassword(), user.getAuthorities());
}

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
throw new BadCredentialsException("login fail! password is null");
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
throw new BadCredentialsException("login fail! password is error");
}
}

@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthToken.class.isAssignableFrom(authentication);
}

public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}

public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}

}

+ 64
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthSecurityConfig.java View File

@@ -0,0 +1,64 @@
package com.ningdatech.pmapi.user.security.auth.password;

import com.ningdatech.pmapi.user.security.auth.config.AuthProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

/**
* 账号密码登陆的认证配置
*/
@Component
public class UsernamePasswordAuthSecurityConfig
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

@Autowired
@Qualifier(value = "defaultLoginSuccessHandler")
protected AuthenticationSuccessHandler defaultLoginSuccessHandler;

@Autowired
@Qualifier(value = "defaultLoginFailureHandler")
protected AuthenticationFailureHandler defaultLoginFailureHandler;

@Autowired
@Qualifier(value = "passwordLoginUserDetailService")
private UserDetailsService passwordLoginUserDetailService;

@Autowired
private PasswordEncoder passwordEncoder;

@Autowired
private AuthProperties authProperties;

private AuthenticationManager authenticationManager;

@Override
public void configure(HttpSecurity http) throws Exception {
UsernamePasswordAuthFilter usernamePasswordAuthFilter =
new UsernamePasswordAuthFilter(authProperties.getPasswordLoginUrl());
authenticationManager = http.getSharedObject(AuthenticationManager.class);
usernamePasswordAuthFilter.setAuthenticationManager(authenticationManager);
usernamePasswordAuthFilter.setAuthenticationSuccessHandler(defaultLoginSuccessHandler);
usernamePasswordAuthFilter.setAuthenticationFailureHandler(defaultLoginFailureHandler);

UsernamePasswordAuthProvider authenticationProvider = new UsernamePasswordAuthProvider();
authenticationProvider.setUserDetailsService(passwordLoginUserDetailService);
// 确保对密码进行加密的encoder和解密的encoder相同
authenticationProvider.setPasswordEncoder(passwordEncoder);
http.authenticationProvider(authenticationProvider).addFilterAfter(usernamePasswordAuthFilter,
UsernamePasswordAuthenticationFilter.class);
}

public AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
}

+ 76
- 0
pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthToken.java View File

@@ -0,0 +1,76 @@
package com.ningdatech.pmapi.user.security.auth.password;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;

import java.util.Collection;

/**
* @Author LiuXinXin
* @Date 2020/8/3 8:52 下午
* @Version 1.0
**/
public class UsernamePasswordAuthToken extends AbstractAuthenticationToken {

private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

private final Object principal;

private final Object credentials;


/**
* This constructor can be safely used by any code that wishes to create a
* <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()} will return
* <code>false</code>.
*/
public UsernamePasswordAuthToken(String principal, String credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}

/**
* This constructor should only be used by <code>AuthenticationManager</code> or <code>AuthenticationProvider</code>
* implementations that are satisfied with producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
* authentication token.
*
* @param principal
* @param authorities
*/
public UsernamePasswordAuthToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
// must use super, as we override
super.setAuthenticated(true);
}

@Override
public Object getCredentials() {
return this.credentials;
}

@Override
public Object getPrincipal() {
return this.principal;
}

@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}

@Override
public void eraseCredentials() {
super.eraseCredentials();
}

}

Loading…
Cancel
Save