Commit ec73b1e7 by tangyi

优化

parent e39e6152
......@@ -46,7 +46,7 @@
| 名称 | 版本 |
| --------- | -------- |
| `Spring Boot` | `2.1.8.RELEASE` |
| `Spring Boot` | `2.1.9.RELEASE` |
| `Spring Cloud` | `Greenwich.SR3` |
# 5 系统架构
......
......@@ -35,5 +35,11 @@
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<!-- jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
</project>
......@@ -7,16 +7,6 @@ package com.github.tangyi.common.security.constant;
public class SecurityConstant {
/**
* 基础角色
*/
public static final String BASE_ROLE = "role_user";
/**
* 超级管理员角色
*/
public static final String ROLE_ADMIN = "role_admin";
/**
* 租户管理员角色
*/
public static final String ROLE_TENANT_ADMIN = "role_tenant_admin";
......@@ -37,11 +27,6 @@ public class SecurityConstant {
public static final String NORMAL = "0";
/**
* 异常状态
*/
public static final String ABNORMAL = "1";
/**
* 手机登录URL
*/
public static final String MOBILE_TOKEN_URL = "/mobile/token";
......
......@@ -15,36 +15,36 @@ public interface CustomUserDetailsService {
/**
* 根据用户名和租户标识查询
*
*
* @param tenantCode tenantCode
* @param username username
* @param tenantCode tenantCode
* @return UserDetails
* @author tangyi
* @date 2019/05/28 21:06
*/
UserDetails loadUserByIdentifierAndTenantCode(String username, String tenantCode) throws UsernameNotFoundException;
UserDetails loadUserByIdentifierAndTenantCode(String tenantCode, String username) throws UsernameNotFoundException;
/**
* 根据社交账号和租户标识查询
*
* @param social social
* @param tenantCode tenantCode
* @param mobileUser mobileUser
* @param social social
* @param mobileUser mobileUser
* @return UserDetails
* @author tangyi
* @date 2019/06/22 21:08
*/
UserDetails loadUserBySocialAndTenantCode(String social, String tenantCode, MobileUser mobileUser) throws UsernameNotFoundException;
UserDetails loadUserBySocialAndTenantCode(String tenantCode, String social, MobileUser mobileUser) throws UsernameNotFoundException;
/**
* 根据微信openId和租户标识查询
*
*
* @param tenantCode tenantCode
* @param code code
* @param tenantCode tenantCode
* @param wxUser wxUser
* @return UserDetails
* @author tangyi
* @date 2019/07/05 20:04:59
*/
UserDetails loadUserByWxCodeAndTenantCode(String code, String tenantCode, WxUser wxUser) throws UsernameNotFoundException;
UserDetails loadUserByWxCodeAndTenantCode(String tenantCode, String code, WxUser wxUser) throws UsernameNotFoundException;
}
package com.github.tangyi.common.security.event;
import lombok.Data;
import org.springframework.context.ApplicationEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
/**
*
* 登录失败事件
*
* @author tangyi
* @date 2019-11-11 23:46
*/
@Data
public class CustomAuthenticationFailureEvent extends ApplicationEvent {
private UserDetails userDetails;
public CustomAuthenticationFailureEvent(Authentication authentication, UserDetails userDetails) {
super(authentication);
this.userDetails = userDetails;
}
}
package com.github.tangyi.common.security.event;
import lombok.Data;
import org.springframework.context.ApplicationEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
/**
* 登录成功事件
*
* @author tangyi
* @date 2019-11-11 23:40
*/
@Data
public class CustomAuthenticationSuccessEvent extends ApplicationEvent {
private UserDetails userDetails;
public CustomAuthenticationSuccessEvent(Authentication authentication, UserDetails userDetails) {
super(authentication);
this.userDetails = userDetails;
}
}
package com.github.tangyi.common.security.exceptions;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.github.tangyi.common.security.serializer.CustomOauthExceptionSerializer;
import lombok.Data;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
/**
* 定制OAuth2Exception
*
* @author tangyi
* @date 2019/3/18 22:36
*/
@Data
@JsonSerialize(using = CustomOauthExceptionSerializer.class)
public class CustomOauthException extends OAuth2Exception {
/**
* 错误码
*/
private int code;
public CustomOauthException(String msg, int code) {
super(msg);
this.code = code;
}
}
package com.github.tangyi.common.security.mobile;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import com.github.tangyi.common.security.event.CustomAuthenticationFailureEvent;
import com.github.tangyi.common.security.event.CustomAuthenticationSuccessEvent;
import com.github.tangyi.common.security.tenant.TenantContextHolder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
......@@ -24,18 +27,22 @@ public class MobileAuthenticationProvider implements AuthenticationProvider {
private CustomUserDetailsService customUserDetailsService;
@Override
private ApplicationEventPublisher publisher;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
MobileAuthenticationToken mobileAuthenticationToken = (MobileAuthenticationToken) authentication;
String principal = mobileAuthenticationToken.getPrincipal().toString();
UserDetails userDetails = customUserDetailsService.loadUserBySocialAndTenantCode(principal, TenantContextHolder.getTenantCode(), mobileAuthenticationToken.getMobileUser());
UserDetails userDetails = customUserDetailsService.loadUserBySocialAndTenantCode(TenantContextHolder.getTenantCode(), principal, mobileAuthenticationToken.getMobileUser());
if (userDetails == null) {
log.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.noopBindAccount", "Noop Bind Account"));
publisher.publishEvent(new CustomAuthenticationFailureEvent(authentication, userDetails));
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.noopBindAccount", "Noop Bind Account"));
}
MobileAuthenticationToken authenticationToken = new MobileAuthenticationToken(userDetails, userDetails.getAuthorities());
authenticationToken.setDetails(mobileAuthenticationToken.getDetails());
return authenticationToken;
publisher.publishEvent(new CustomAuthenticationSuccessEvent(authentication, userDetails));
return authenticationToken;
}
@Override
......
......@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
......@@ -27,6 +28,9 @@ public class MobileSecurityConfigurer extends SecurityConfigurerAdapter<DefaultS
@Autowired
private AuthenticationEventPublisher defaultAuthenticationEventPublisher;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
private AuthenticationSuccessHandler mobileLoginSuccessHandler;
private CustomUserDetailsService userDetailsService;
......@@ -40,6 +44,7 @@ public class MobileSecurityConfigurer extends SecurityConfigurerAdapter<DefaultS
mobileAuthenticationFilter.setEventPublisher(defaultAuthenticationEventPublisher);
MobileAuthenticationProvider mobileAuthenticationProvider = new MobileAuthenticationProvider();
mobileAuthenticationProvider.setCustomUserDetailsService(userDetailsService);
mobileAuthenticationProvider.setPublisher(applicationEventPublisher);
// 增加手机登录的过滤器
http.authenticationProvider(mobileAuthenticationProvider).addFilterAfter(mobileAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
......
package com.github.tangyi.common.security.serializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.github.tangyi.common.security.exceptions.CustomOauthException;
import java.io.IOException;
import java.util.Map;
/**
* 定制OauthException序列化
*
* @author tangyi
* @date 2019/3/18 22:37
*/
public class CustomOauthExceptionSerializer extends StdSerializer<CustomOauthException> {
public CustomOauthExceptionSerializer() {
super(CustomOauthException.class);
}
@Override
public void serialize(CustomOauthException value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
// 封装属性,和ResponseBean对应
gen.writeNumberField("status", value.getCode());
// 返回给前端的code
gen.writeNumberField("code", value.getCode());
// 提示信息
gen.writeStringField("msg", value.getMessage());
if (value.getAdditionalInformation() != null) {
for (Map.Entry<String, String> entry : value.getAdditionalInformation().entrySet()) {
String key = entry.getKey();
String add = entry.getValue();
gen.writeStringField(key, add);
}
}
gen.writeEndObject();
}
}
package com.github.tangyi.common.security.wx;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import com.github.tangyi.common.security.event.CustomAuthenticationFailureEvent;
import com.github.tangyi.common.security.event.CustomAuthenticationSuccessEvent;
import com.github.tangyi.common.security.tenant.TenantContextHolder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
......@@ -24,6 +27,8 @@ public class WxAuthenticationProvider implements AuthenticationProvider {
private CustomUserDetailsService customUserDetailsService;
private ApplicationEventPublisher publisher;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WxAuthenticationToken wxAuthenticationToken = (WxAuthenticationToken) authentication;
......@@ -32,11 +37,13 @@ public class WxAuthenticationProvider implements AuthenticationProvider {
UserDetails userDetails = customUserDetailsService.loadUserByWxCodeAndTenantCode(principal, TenantContextHolder.getTenantCode(), wxAuthenticationToken.getWxUser());
if (userDetails == null) {
log.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.noopBindAccount", "Noop Bind Account"));
publisher.publishEvent(new CustomAuthenticationFailureEvent(authentication, userDetails));
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.noopBindAccount", "Noop Bind Account"));
}
WxAuthenticationToken authenticationToken = new WxAuthenticationToken(userDetails, userDetails.getAuthorities());
authenticationToken.setDetails(wxAuthenticationToken.getDetails());
return authenticationToken;
publisher.publishEvent(new CustomAuthenticationSuccessEvent(authentication, userDetails));
return authenticationToken;
}
@Override
......
......@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
......@@ -27,6 +28,9 @@ public class WxSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecur
@Autowired
private AuthenticationEventPublisher defaultAuthenticationEventPublisher;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
private AuthenticationSuccessHandler wxLoginSuccessHandler;
private CustomUserDetailsService userDetailsService;
......@@ -40,6 +44,7 @@ public class WxSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecur
wxAuthenticationFilter.setEventPublisher(defaultAuthenticationEventPublisher);
WxAuthenticationProvider wxAuthenticationProvider = new WxAuthenticationProvider();
wxAuthenticationProvider.setCustomUserDetailsService(userDetailsService);
wxAuthenticationProvider.setPublisher(applicationEventPublisher);
// 增加微信登录的过滤器
http.authenticationProvider(wxAuthenticationProvider).addFilterAfter(wxAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
......
package com.github.tangyi.auth.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
*
* 配置aop
*
* @author tangyi
* @date 2019-11-12 20:13
*/
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}
......@@ -2,16 +2,12 @@ package com.github.tangyi.auth.config;
import com.github.tangyi.auth.security.CustomTokenConverter;
import com.github.tangyi.common.security.core.ClientDetailsServiceImpl;
import com.github.tangyi.common.security.exceptions.CustomOauthException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
......@@ -111,18 +107,7 @@ public class CustomAuthorizationServerConfigurer extends AuthorizationServerConf
// 将token存储到redis
.tokenStore(tokenStore())
// token增强
.tokenEnhancer(jwtTokenEnhancer())
// 异常处理
.exceptionTranslator(e -> {
if (e instanceof OAuth2Exception) {
OAuth2Exception oAuth2Exception = (OAuth2Exception) e;
return ResponseEntity
.status(oAuth2Exception.getHttpErrorCode())
.body(new CustomOauthException(oAuth2Exception.getMessage(), oAuth2Exception.getHttpErrorCode()));
} else {
throw e;
}
});
.tokenEnhancer(jwtTokenEnhancer());
}
/**
......@@ -140,7 +125,5 @@ public class CustomAuthorizationServerConfigurer extends AuthorizationServerConf
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
}
package com.github.tangyi.auth.config;
import com.github.tangyi.auth.error.CustomOAuth2AccessDeniedHandler;
import com.github.tangyi.auth.security.CustomUserDetailsAuthenticationProvider;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
......@@ -15,6 +17,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
import org.springframework.security.web.access.AccessDeniedHandler;
/**
* Spring Security配置
......@@ -33,6 +36,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthorizationServerEndpointsConfiguration endpoints;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
......@@ -46,6 +52,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
// 认证管理器
endpoints.getEndpointsConfigurer().authenticationManager(authenticationManager());
// accessDeniedHandler
http.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler());
}
@Bean
......@@ -65,7 +75,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
*/
@Bean
public AuthenticationProvider authProvider() {
return new CustomUserDetailsAuthenticationProvider(encoder(), userDetailsService);
return new CustomUserDetailsAuthenticationProvider(encoder(), userDetailsService, applicationEventPublisher);
}
@Override
......@@ -73,5 +83,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomOAuth2AccessDeniedHandler();
}
}
package com.github.tangyi.auth.error;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.oauth2.provider.error.AbstractOAuth2SecurityExceptionHandler;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
*
* accessDeniedHandler,return ResponseBean
*
* @author tangyi
* @date 2019-11-11 21:24
*/
@Slf4j
public class CustomOAuth2AccessDeniedHandler extends AbstractOAuth2SecurityExceptionHandler
implements AccessDeniedHandler {
private WebResponseExceptionTranslator<?> exceptionTranslator = new DefaultWebResponseExceptionTranslator();
private CustomOAuth2ExceptionRenderer exceptionRenderer = new CustomOAuth2ExceptionRenderer();
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authException)
throws IOException {
try {
ResponseEntity<?> result = exceptionTranslator.translate(authException);
result = enhanceResponse(result, authException);
exceptionRenderer.handleResponseBeanResponse(result, new ServletWebRequest(request, response));
response.flushBuffer();
} catch (ServletException e) {
log.error(e.getMessage(), e);
} catch (IOException | RuntimeException e) {
throw e;
} catch (Exception e) {
// Wrap other Exceptions. These are not expected to happen
throw new RuntimeException(e);
}
}
}
package com.github.tangyi.auth.error;
import com.github.tangyi.common.core.model.ResponseBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.oauth2.http.converter.jaxb.JaxbOAuth2ExceptionMessageConverter;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
*
* render异常
*
* @author tangyi
* @date 2019-11-11 21:29
*/
@Slf4j
public class CustomOAuth2ExceptionRenderer {
private List<HttpMessageConverter<?>> messageConverters = geDefaultMessageConverters();
public void handleResponseBeanResponse(ResponseEntity<?> responseEntity, ServletWebRequest webRequest)
throws Exception {
if (responseEntity == null) {
return;
}
HttpInputMessage inputMessage = createHttpInputMessage(webRequest);
HttpOutputMessage outputMessage = createHttpOutputMessage(webRequest);
if (outputMessage instanceof ServerHttpResponse) {
((ServerHttpResponse) outputMessage).setStatusCode(responseEntity.getStatusCode());
}
HttpHeaders entityHeaders = responseEntity.getHeaders();
if (!entityHeaders.isEmpty()) {
outputMessage.getHeaders().putAll(entityHeaders);
}
Object body = responseEntity.getBody();
if (body != null) {
// 返回ResponseBean
ResponseBean<?> responseBean = new ResponseBean<>(body);
responseBean.setStatus(responseEntity.getStatusCode().value());
writeWithMessageConverters(responseBean, inputMessage, outputMessage);
} else {
// flush headers
outputMessage.getBody();
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void writeWithMessageConverters(Object returnValue, HttpInputMessage inputMessage,
HttpOutputMessage outputMessage) throws IOException, HttpMediaTypeNotAcceptableException {
List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
if (acceptedMediaTypes.isEmpty()) {
acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
}
MediaType.sortByQualityValue(acceptedMediaTypes);
Class<?> returnValueType = returnValue.getClass();
List<MediaType> allSupportedMediaTypes = new ArrayList<>();
for (MediaType acceptedMediaType : acceptedMediaTypes) {
for (HttpMessageConverter messageConverter : messageConverters) {
if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
messageConverter.write(returnValue, acceptedMediaType, outputMessage);
if (log.isDebugEnabled()) {
MediaType contentType = outputMessage.getHeaders().getContentType();
if (contentType == null) {
contentType = acceptedMediaType;
}
log.debug("Written [" + returnValue + "] as \"" + contentType + "\" using [" + messageConverter
+ "]");
}
return;
}
}
}
for (HttpMessageConverter messageConverter : messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
}
throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
}
private List<HttpMessageConverter<?>> geDefaultMessageConverters() {
List<HttpMessageConverter<?>> result = new ArrayList<>(new RestTemplate().getMessageConverters());
result.add(new JaxbOAuth2ExceptionMessageConverter());
return result;
}
private HttpInputMessage createHttpInputMessage(NativeWebRequest webRequest) {
return new ServletServerHttpRequest(webRequest.getNativeRequest(HttpServletRequest.class));
}
private HttpOutputMessage createHttpOutputMessage(NativeWebRequest webRequest) {
return new ServletServerHttpResponse((HttpServletResponse) webRequest.getNativeResponse());
}
}
package com.github.tangyi.auth.filter;
import com.github.tangyi.auth.model.CustomUserDetails;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.constant.ServiceConstant;
import com.github.tangyi.common.core.model.Log;
import com.github.tangyi.common.core.utils.SysUtil;
import com.github.tangyi.user.api.feign.UserServiceClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpointAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
*
* 登录成功后的处理,如记录登录日志
*
* @author tangyi
* @date 2019-10-11 12:08
*/
@Slf4j
public class CustomTokenEndpointAuthenticationFilter extends TokenEndpointAuthenticationFilter {
private UserServiceClient userServiceClient;
public CustomTokenEndpointAuthenticationFilter(AuthenticationManager authenticationManager,
OAuth2RequestFactory oAuth2RequestFactory, UserServiceClient userServiceClient) {
super(authenticationManager, oAuth2RequestFactory);
this.userServiceClient = userServiceClient;
}
@Override
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
// 登录成功后的处理
log.info("CustomTokenEndpointAuthenticationFilter onSuccessfulAuthentication");
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
String tenantCode = userDetails.getTenantCode();
String username = userDetails.getUsername();
log.info("CustomTokenEndpointAuthenticationFilter 登录成功, tenantCode: {}, username: {}", tenantCode, username);
// 记录日志
Log logInfo = new Log();
logInfo.setCommonValue(username, SysUtil.getSysCode(), tenantCode);
logInfo.setTitle("用户登录");
logInfo.setType(CommonConstant.STATUS_NORMAL);
logInfo.setMethod(request.getMethod());
logInfo.setTime(String.valueOf(System.currentTimeMillis() - userDetails.getStart()));
logInfo.setRequestUri(request.getRequestURI());
// 获取ip、浏览器信息
logInfo.setIp(request.getRemoteAddr());
logInfo.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
logInfo.setServiceId(ServiceConstant.AUTH_SERVICE);
// 记录日志
saveLoginSuccessLog(logInfo);
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
// 认证前的特殊处理
// if (!condition) {
// throw new AuthenticationServiceException("condition not satisfied");
// }
log.info("CustomTokenEndpointAuthenticationFilter doFilter");
super.doFilter(req, res, chain);
}
/**
* 异步记录登录日志
*
* @author tangyi
* @date 2019/05/30 23:30
*/
@Async
public void saveLoginSuccessLog(Log logInfo) {
try {
userServiceClient.saveLog(logInfo);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
package com.github.tangyi.auth.listener;
import com.github.tangyi.common.security.event.CustomAuthenticationFailureEvent;
import com.github.tangyi.user.api.feign.UserServiceClient;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
*
* 处理登录失败事件
*
* @author tangyi
* @date 2019-11-11 23:52
*/
@Slf4j
@AllArgsConstructor
@Component
public class LoginFailureListener implements ApplicationListener<CustomAuthenticationFailureEvent> {
private final UserServiceClient userServiceClient;
@Override
public void onApplicationEvent(CustomAuthenticationFailureEvent event) {
// 登录失败后的处理
}
}
package com.github.tangyi.auth.listener;
import com.github.tangyi.auth.model.CustomUserDetails;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.constant.ServiceConstant;
import com.github.tangyi.common.core.model.Log;
import com.github.tangyi.common.core.utils.DateUtils;
import com.github.tangyi.common.core.utils.SysUtil;
import com.github.tangyi.common.security.event.CustomAuthenticationSuccessEvent;
import com.github.tangyi.user.api.dto.UserDto;
import com.github.tangyi.user.api.feign.UserServiceClient;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
/**
*
* 处理登录成功事件
*
* @author tangyi
* @date 2019-11-11 23:48
*/
@Slf4j
@AllArgsConstructor
@Component
public class LoginSuccessListener implements ApplicationListener<CustomAuthenticationSuccessEvent> {
private final UserServiceClient userServiceClient;
@Override
public void onApplicationEvent(CustomAuthenticationSuccessEvent event) {
// 登录成功后的处理
UserDetails userDetails = event.getUserDetails();
if (userDetails instanceof CustomUserDetails) {
CustomUserDetails customUserDetails = (CustomUserDetails) userDetails;
String tenantCode = customUserDetails.getTenantCode();
String username = userDetails.getUsername();
log.info("{} login success, tenantCode: {}", username, tenantCode);
// 记录日志
Log logInfo = new Log();
logInfo.setTitle("用户登录");
logInfo.setCommonValue(username, SysUtil.getSysCode(), tenantCode);
logInfo.setTime(String.valueOf(System.currentTimeMillis() - customUserDetails.getStart()));
logInfo.setType(CommonConstant.STATUS_NORMAL);
HttpServletRequest request = currentRequestAttributes().getRequest();
logInfo.setMethod(request.getMethod());
logInfo.setRequestUri(request.getRequestURI());
// 获取ip、浏览器信息
logInfo.setIp(request.getRemoteAddr());
logInfo.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
logInfo.setServiceId(ServiceConstant.AUTH_SERVICE);
// 记录日志和登录时间
UserDto userDto = new UserDto();
userDto.setIdentifier(username);
userDto.setLoginTime(DateUtils.asDate(LocalDateTime.now()));
saveLoginInfo(logInfo, userDto);
}
}
/**
* 异步记录登录日志
*
* @param logInfo logInfo
* @param userDto userDto
* @author tangyi
* @date 2019/05/30 23:30
*/
@Async
public void saveLoginInfo(Log logInfo, UserDto userDto) {
try {
userServiceClient.saveLog(logInfo);
//userServiceClient.updateUser(userDto);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
*
* 获取当前request
*
* @return ServletRequestAttributes
* @author tangyi
* @date 2019-11-12 00:15
*/
private static ServletRequestAttributes currentRequestAttributes() {
RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
if (!(requestAttr instanceof ServletRequestAttributes)) {
throw new IllegalStateException("Current request is not a servlet request");
}
return (ServletRequestAttributes) requestAttr;
}
}
package com.github.tangyi.auth.security;
import com.github.tangyi.common.security.event.CustomAuthenticationFailureEvent;
import com.github.tangyi.common.security.event.CustomAuthenticationSuccessEvent;
import com.github.tangyi.common.core.exceptions.TenantNotFoundException;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import com.github.tangyi.common.security.tenant.TenantContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
......@@ -30,9 +33,12 @@ public class CustomUserDetailsAuthenticationProvider extends AbstractUserDetails
private String userNotFoundEncodedPassword;
public CustomUserDetailsAuthenticationProvider(PasswordEncoder passwordEncoder, CustomUserDetailsService userDetailsService) {
private ApplicationEventPublisher publisher;
public CustomUserDetailsAuthenticationProvider(PasswordEncoder passwordEncoder, CustomUserDetailsService userDetailsService, ApplicationEventPublisher publisher) {
this.passwordEncoder = passwordEncoder;
this.userDetailsService = userDetailsService;
this.publisher = publisher;
}
@Override
......@@ -46,9 +52,11 @@ public class CustomUserDetailsAuthenticationProvider extends AbstractUserDetails
// 匹配密码
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
log.debug("认证失败: 密码错误.");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "密码错误."));
publisher.publishEvent(new CustomAuthenticationFailureEvent(authentication, userDetails));
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "密码错误."));
}
}
publisher.publishEvent(new CustomAuthenticationSuccessEvent(authentication, userDetails));
}
@Override
protected void doAfterPropertiesSet() throws Exception {
......@@ -70,9 +78,9 @@ public class CustomUserDetailsAuthenticationProvider extends AbstractUserDetails
UserDetails loadedUser;
try {
// 加载用户信息
loadedUser = this.userDetailsService.loadUserByIdentifierAndTenantCode(authentication.getPrincipal().toString(), TenantContextHolder.getTenantCode());
loadedUser = this.userDetailsService.loadUserByIdentifierAndTenantCode(TenantContextHolder.getTenantCode(), authentication.getPrincipal().toString());
} catch (TenantNotFoundException tenantNotFound) {
throw tenantNotFound;
throw new InternalAuthenticationServiceException(tenantNotFound.getMessage(), tenantNotFound);
} catch (UsernameNotFoundException notFound) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
......
......@@ -49,14 +49,14 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
/**
* 加载用户信息
*
* @param tenantCode 租户标识
* @param username 用户名
* @return UserDetails
* @throws UsernameNotFoundException,TenantNotFoundException
*/
@Override
public UserDetails loadUserByIdentifierAndTenantCode(String username, String tenantCode) throws UsernameNotFoundException, TenantNotFoundException {
public UserDetails loadUserByIdentifierAndTenantCode(String tenantCode, String username) throws UsernameNotFoundException, TenantNotFoundException {
long start = System.currentTimeMillis();
Tenant tenant = this.validateTenantCode(tenantCode);
ResponseBean<UserVo> userVoResponseBean = userServiceClient.findUserByIdentifier(username, tenantCode);
if (!ResponseUtil.isSuccess(userVoResponseBean))
throw new ServiceException("查询用户信息失败: " + userVoResponseBean.getMsg());
......@@ -68,18 +68,17 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
/**
* 根据社交账号查询
*
*
* @param tenantCode tenantCode
* @param social social
* @param tenantCode tenantCode
* @param mobileUser mobileUser
* @return UserDetails
* @author tangyi
* @date 2019/06/22 21:08
*/
@Override
public UserDetails loadUserBySocialAndTenantCode(String social, String tenantCode, MobileUser mobileUser) throws UsernameNotFoundException {
public UserDetails loadUserBySocialAndTenantCode(String tenantCode, String social, MobileUser mobileUser) throws UsernameNotFoundException {
long start = System.currentTimeMillis();
Tenant tenant = this.validateTenantCode(tenantCode);
ResponseBean<UserVo> userVoResponseBean = userServiceClient.findUserByIdentifier(social, IdentityType.PHONE_NUMBER.getValue(), tenantCode);
if (!ResponseUtil.isSuccess(userVoResponseBean))
throw new ServiceException("查询用户信息失败: " + userVoResponseBean.getMsg());
......@@ -103,12 +102,6 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
if (!ResponseUtil.isSuccess(userVoResponseBean))
throw new ServiceException("查询用户信息失败: " + userVoResponseBean.getMsg());
userVo = userVoResponseBean.getData();
} else {
// TODO 记录登录时间,IP等信息
UserDto userDto = new UserDto();
BeanUtils.copyProperties(userVo, userDto);
userDto.setLoginTime(DateUtils.asDate(LocalDateTime.now()));
//userServiceClient.updateUser(userDto);
}
return new CustomUserDetails(userVo.getIdentifier(), userVo.getCredential(), CommonConstant.STATUS_NORMAL.equals(userVo.getStatus()), getAuthority(userVo), userVo.getTenantCode(), start, LoginType.SMS);
}
......@@ -117,17 +110,16 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
* 根据微信code和租户标识查询
* 将code换成openId和sessionKey
*
* @param tenantCode tenantCode
* @param code code
* @param tenantCode tenantCode
* @param wxUser wxUser
* @return UserDetails
* @author tangyi
* @date 2019/07/05 20:05:36
*/
@Override
public UserDetails loadUserByWxCodeAndTenantCode(String code, String tenantCode, WxUser wxUser) throws UsernameNotFoundException {
public UserDetails loadUserByWxCodeAndTenantCode(String tenantCode, String code, WxUser wxUser) throws UsernameNotFoundException {
long start = System.currentTimeMillis();
Tenant tenant = this.validateTenantCode(tenantCode);
// 根据code获取openId和sessionKey
WxSession wxSession = wxService.code2Session(code);
if (wxSession == null)
......@@ -156,37 +148,11 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
if (!ResponseUtil.isSuccess(userVoResponseBean))
throw new ServiceException("查询用户信息失败: " + userVoResponseBean.getMsg());
userVo = userVoResponseBean.getData();
} else {
// TODO 更新sessionKey,记录登录时间,IP等信息
UserDto userDto = new UserDto();
BeanUtils.copyProperties(userVo, userDto);
//userDto.setCredential(wxSession.getSessionKey());
userDto.setLoginTime(DateUtils.asDate(LocalDateTime.now()));
//userServiceClient.updateUser(userDto);
}
return new CustomUserDetails(userVo.getIdentifier(), userVo.getCredential(), CommonConstant.STATUS_NORMAL.equals(userVo.getStatus()), getAuthority(userVo), userVo.getTenantCode(), start, LoginType.WECHAT);
}
/**
* 校验租户标识
*
* @param tenantCode tenantCode
* @return Tenant
*/
private Tenant validateTenantCode(String tenantCode) throws TenantNotFoundException {
if (StringUtils.isBlank(tenantCode))
throw new TenantNotFoundException("租户code不能为空.");
// 先获取租户信息
ResponseBean<Tenant> tenantResponseBean = userServiceClient.findTenantByTenantCode(tenantCode);
if (!ResponseUtil.isSuccess(tenantResponseBean))
throw new ServiceException("查询租户信息失败: " + tenantResponseBean.getMsg());
Tenant tenant = tenantResponseBean.getData();
if (tenant == null)
throw new TenantNotFoundException("租户不存在.");
return tenant;
}
/**
* 获取用户权限
*
* @param userVo userVo
......
package com.github.tangyi.auth.security;
import com.github.tangyi.common.core.exceptions.ServiceException;
import com.github.tangyi.common.core.exceptions.TenantNotFoundException;
import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.core.utils.ResponseUtil;
import com.github.tangyi.user.api.feign.UserServiceClient;
import com.github.tangyi.user.api.module.Tenant;
import lombok.AllArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
*
* 校验租户
*
* @author tangyi
* @date 2019-11-12 20:14
*/
@AllArgsConstructor
@Aspect
@Component
public class ValidateTenantAspect {
private final UserServiceClient userServiceClient;
@Before("execution(* com.github.tangyi.auth.security.CustomUserDetailsServiceImpl.load*(..)) && args(tenantCode,..)")
public void validateTenantCode(String tenantCode) throws TenantNotFoundException {
// 获取tenantCode
if (StringUtils.isBlank(tenantCode))
throw new TenantNotFoundException("租户code不能为空.");
// 先获取租户信息
ResponseBean<Tenant> tenantResponseBean = userServiceClient.findTenantByTenantCode(tenantCode);
if (!ResponseUtil.isSuccess(tenantResponseBean))
throw new ServiceException("查询租户信息失败: " + tenantResponseBean.getMsg());
Tenant tenant = tenantResponseBean.getData();
if (tenant == null)
throw new TenantNotFoundException("租户不存在.");
}
}
......@@ -97,10 +97,6 @@ public class LogController extends BaseController {
@ApiOperation(value = "新增日志", notes = "新增日志")
@ApiImplicitParam(name = "log", value = "日志实体Log", required = true, dataType = "Log")
public ResponseBean<Boolean> addLog(@RequestBody @Valid Log log) {
if (log.getId() != null)
log.setCommonValue(SysUtil.getUser(), SysUtil.getSysCode(), SysUtil.getTenantCode());
if (true)
return null;
// 保存日志
return new ResponseBean<>(logService.insert(log) > 0);
}
......
......@@ -72,6 +72,7 @@
<okhttp.version>3.8.1</okhttp.version>
<aliyun.version>4.0.3</aliyun.version>
<weixin.version>3.4.0</weixin.version>
<jjwt.version>0.9.0</jjwt.version>
<!-- docker -->
<docker.maven.verion>1.4.3</docker.maven.verion>
......@@ -301,6 +302,13 @@
<artifactId>weixin-java-pay</artifactId>
<version>${weixin.version}</version>
</dependency>
<!-- jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment