diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/common/constants/BizConst.java b/pmapi/src/main/java/com/ningdatech/pmapi/common/constants/BizConst.java new file mode 100644 index 0000000..a4eb44c --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/common/constants/BizConst.java @@ -0,0 +1,98 @@ +package com.ningdatech.pmapi.common.constants; + +import com.ningdatech.basic.model.ApiResponse; + +import java.math.BigDecimal; + +/** + *

+ * 业务常量 + *

+ * + * @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 UNAUTHENTICATED = ApiResponse.of(401, "用户未登录", null); + + int MAX_EXPORT_COUNT = 5000; + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/common/errorcode/AppErrorCode.java b/pmapi/src/main/java/com/ningdatech/pmapi/common/errorcode/AppErrorCode.java new file mode 100644 index 0000000..40a8f06 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/common/errorcode/AppErrorCode.java @@ -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; + } +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/WebSecurityConfig.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/WebSecurityConfig.java new file mode 100644 index 0000000..58c2aa3 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/WebSecurityConfig.java @@ -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 roleArrayMap = authProperties.getRoleArrayMap(); + Set roleSet = roleArrayMap.keySet(); + for (String role : roleSet) { + String[] urlsArray = roleArrayMap.get(role); + if (urlsArray != null && urlsArray.length > 0) { + http.authorizeRequests().antMatchers(urlsArray).hasAuthority(role); + } + } + } + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/AuthProperties.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/AuthProperties.java new file mode 100644 index 0000000..8d0be9e --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/AuthProperties.java @@ -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 ignoreAuthUrls; + + private List ignoreCsrfUrls; + + private Map> 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 getRoleArrayMap() { + Map roleArrayMap = new HashMap<>(); + if (Objects.nonNull(roleMap)) { + Set keySet = roleMap.keySet(); + for (String key : keySet) { + List urls = roleMap.get(key); + if (CollectionUtil.isNotEmpty(urls)) { + String[] stringArray = new String[urls.size()]; + roleArrayMap.put(key, urls.toArray(stringArray)); + } + } + } + return roleArrayMap; + } +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/AuthenticationBeanConfig.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/AuthenticationBeanConfig.java new file mode 100644 index 0000000..37d7da7 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/config/AuthenticationBeanConfig.java @@ -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(); + } + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/errorcode/AuthErrorCodeEnum.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/errorcode/AuthErrorCodeEnum.java new file mode 100644 index 0000000..5812ab1 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/errorcode/AuthErrorCodeEnum.java @@ -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); + } +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultExpiredSessionStrategy.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultExpiredSessionStrategy.java new file mode 100644 index 0000000..5024ae8 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultExpiredSessionStrategy.java @@ -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))); + } +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginFailureHandler.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginFailureHandler.java new file mode 100644 index 0000000..cf1ebc7 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginFailureHandler.java @@ -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))); + } + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginSuccessHandler.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginSuccessHandler.java new file mode 100644 index 0000000..d0c5ca9 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLoginSuccessHandler.java @@ -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())); + } + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLogoutSuccessHandler.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLogoutSuccessHandler.java new file mode 100644 index 0000000..b7e3a45 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/handler/DefaultLogoutSuccessHandler.java @@ -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())); + } +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/model/UserInfoDetails.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/model/UserInfoDetails.java new file mode 100644 index 0000000..b497f95 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/model/UserInfoDetails.java @@ -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 responsibleCompanyIdList; + + /** + * 获取用户权限 + * + * @return + */ + @Override + public Collection 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; + } + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/PasswordLoginUserDetailService.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/PasswordLoginUserDetailService.java new file mode 100644 index 0000000..fdd0c52 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/PasswordLoginUserDetailService.java @@ -0,0 +1,40 @@ +package com.ningdatech.pmapi.user.security.auth.password; + + +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 UserAuthLoginFacade userAuthLoginFacade; + + @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; + } +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthFilter.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthFilter.java new file mode 100644 index 0000000..dfddb8b --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthFilter.java @@ -0,0 +1,74 @@ +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; + + // ~ 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 usernameParameter = "username"; + String username = request.getParameter(usernameParameter); + String passwordParameter = "password"; + String password = request.getParameter(passwordParameter); + String loginPlatformParameter = "loginPlatform"; + String loginPlatform = request.getParameter(loginPlatformParameter); + if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { + throw new UsernameNotFoundException("用户名或密码不能为空"); + } + if (StringUtils.isBlank(loginPlatform)) { + throw new BizException("登录平台类型不能为空"); + } + 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)); + } +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthProvider.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthProvider.java new file mode 100644 index 0000000..8c7adae --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthProvider.java @@ -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; + } + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthSecurityConfig.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthSecurityConfig.java new file mode 100644 index 0000000..da48c8d --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthSecurityConfig.java @@ -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 { + + @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; + } +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthToken.java b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthToken.java new file mode 100644 index 0000000..7be34c1 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/user/security/auth/password/UsernamePasswordAuthToken.java @@ -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 + * UsernamePasswordAuthenticationToken, as the {@link #isAuthenticated()} will return + * false. + */ + public UsernamePasswordAuthToken(String principal, String credentials) { + super(null); + this.principal = principal; + this.credentials = credentials; + setAuthenticated(false); + } + + /** + * This constructor should only be used by AuthenticationManager or AuthenticationProvider + * implementations that are satisfied with producing a trusted (i.e. {@link #isAuthenticated()} = true) + * authentication token. + * + * @param principal + * @param authorities + */ + public UsernamePasswordAuthToken(Object principal, Object credentials, + Collection 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(); + } + +}