@@ -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; | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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))); | |||
} | |||
} |
@@ -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))); | |||
} | |||
} |
@@ -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())); | |||
} | |||
} |
@@ -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())); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |