diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/po/User.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/po/User.java index 91f52b8..c296839 100644 --- a/flyfish-data/src/main/java/com/flyfish/framework/domain/po/User.java +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/po/User.java @@ -96,6 +96,11 @@ public class User extends AuditDomain implements IUser { */ private Integer errorCount = 0; + /** + * 上次登录日期 + */ + private Date lastTime; + @Override public User toUser() { return this; diff --git a/flyfish-logging/src/main/java/com/flyfish/framework/logging/config/LoggingTextRegistry.java b/flyfish-logging/src/main/java/com/flyfish/framework/logging/config/LoggingTextRegistry.java index 2b9a2b6..cefe2b5 100644 --- a/flyfish-logging/src/main/java/com/flyfish/framework/logging/config/LoggingTextRegistry.java +++ b/flyfish-logging/src/main/java/com/flyfish/framework/logging/config/LoggingTextRegistry.java @@ -1,6 +1,5 @@ package com.flyfish.framework.logging.config; -import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.InitializingBean; import java.util.HashMap; @@ -27,6 +26,8 @@ public class LoggingTextRegistry implements InitializingBean { this.mapping.put("UPDATE_ALL", "更新全部"); this.mapping.put("DELETE", "删除"); this.mapping.put("SYNC", "同步"); + this.mapping.put("LOGIN", "系统登录"); + this.mapping.put("LOGOUT", "退出登录"); } public String text(String code) { diff --git a/flyfish-logging/src/main/java/com/flyfish/framework/logging/domain/Log.java b/flyfish-logging/src/main/java/com/flyfish/framework/logging/domain/Log.java index acb02b0..7aa7574 100644 --- a/flyfish-logging/src/main/java/com/flyfish/framework/logging/domain/Log.java +++ b/flyfish-logging/src/main/java/com/flyfish/framework/logging/domain/Log.java @@ -15,6 +15,9 @@ import java.util.Date; @EqualsAndHashCode(callSuper = true) public class Log extends Domain { + // 日志类型 + private LogType type; + // 业务 private String business; diff --git a/flyfish-logging/src/main/java/com/flyfish/framework/logging/domain/LogQo.java b/flyfish-logging/src/main/java/com/flyfish/framework/logging/domain/LogQo.java index ea86ce4..49b578b 100644 --- a/flyfish-logging/src/main/java/com/flyfish/framework/logging/domain/LogQo.java +++ b/flyfish-logging/src/main/java/com/flyfish/framework/logging/domain/LogQo.java @@ -5,6 +5,7 @@ import com.flyfish.framework.domain.base.BaseQo; import com.flyfish.framework.domain.base.NameLikeQo; import lombok.Getter; import lombok.Setter; +import org.springframework.data.domain.Sort; import java.util.List; @@ -25,11 +26,18 @@ public class LogQo extends NameLikeQo { private List range; + private String type; + @Override public CriteriaBuilder criteriaBuilder() { return super.criteriaBuilder() .with("operator", CriteriaBuilder.Builders.LIKE) - .with("module", "success") + .with("type", "module", "success") .with("range", "startTime", CriteriaBuilder.Builders.DATE_RANGE); } + + @Override + public Sort sorts() { + return Sort.by(Sort.Order.desc("startTime")); + } } diff --git a/flyfish-logging/src/main/java/com/flyfish/framework/logging/domain/LogType.java b/flyfish-logging/src/main/java/com/flyfish/framework/logging/domain/LogType.java new file mode 100644 index 0000000..937a4d0 --- /dev/null +++ b/flyfish-logging/src/main/java/com/flyfish/framework/logging/domain/LogType.java @@ -0,0 +1,14 @@ +package com.flyfish.framework.logging.domain; + +import com.flyfish.framework.enums.NamedEnum; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum LogType implements NamedEnum { + + OPERATION("操作"), AUTHENTICATION("登录"); + + private final String name; +} diff --git a/flyfish-logging/src/main/java/com/flyfish/framework/logging/service/LogManager.java b/flyfish-logging/src/main/java/com/flyfish/framework/logging/service/LogManager.java index 26e6f76..9073859 100644 --- a/flyfish-logging/src/main/java/com/flyfish/framework/logging/service/LogManager.java +++ b/flyfish-logging/src/main/java/com/flyfish/framework/logging/service/LogManager.java @@ -3,6 +3,7 @@ package com.flyfish.framework.logging.service; import com.alibaba.fastjson.JSON; import com.flyfish.framework.logging.config.LoggingTextRegistry; import com.flyfish.framework.logging.domain.Log; +import com.flyfish.framework.logging.domain.LogType; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -38,6 +39,7 @@ public class LogManager implements DisposableBean { executorService.execute(() -> { // 构建日志 Log log = new Log(); + log.setType(LogType.OPERATION); log.setSuccess(context.isSuccess()); log.setBody(bodyString(context.getArgs())); log.setModule(context.getModule()); diff --git a/flyfish-logging/src/main/java/com/flyfish/framework/logging/service/SimpleAuthenticationLogger.java b/flyfish-logging/src/main/java/com/flyfish/framework/logging/service/SimpleAuthenticationLogger.java new file mode 100644 index 0000000..86750c2 --- /dev/null +++ b/flyfish-logging/src/main/java/com/flyfish/framework/logging/service/SimpleAuthenticationLogger.java @@ -0,0 +1,116 @@ +package com.flyfish.framework.logging.service; + +import com.flyfish.framework.domain.base.Domain; +import com.flyfish.framework.domain.po.User; +import com.flyfish.framework.logging.config.LoggingTextRegistry; +import com.flyfish.framework.logging.domain.Log; +import com.flyfish.framework.logging.domain.LogType; +import com.flyfish.framework.service.AuthenticationLogger; +import com.flyfish.framework.service.UserFindService; +import com.flyfish.framework.utils.AuthenticationMessages; +import com.flyfish.framework.utils.UserUtils; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; +import org.springframework.util.MultiValueMap; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.Optional; + +/** + * 简单的认证日志 + * + * @author wangyu + */ +@Service +public class SimpleAuthenticationLogger implements AuthenticationLogger { + + @Resource + private LogService logService; + @Resource + private UserFindService userService; + @Resource + private LoggingTextRegistry registry; + + /** + * 记录成功 + * + * @param user 用户 + */ + @Override + public void success(UserDetails user) { + saveSuccess("LOGIN", "登录成功", user); + } + + /** + * 记录失败 + * + * @param form 表单 + * @param exception 错误异常 + */ + @Override + public void failure(MultiValueMap form, AuthenticationException exception) { + saveError("LOGIN", "登录失败", form.getFirst("username"), AuthenticationMessages.message(exception)); + } + + /** + * 记录退出 + * + * @param user 用户 + */ + @Override + public void logout(UserDetails user) { + saveSuccess("LOGOUT", "退出成功", user); + } + + /** + * 操作成功记录 + * + * @param business 业务 + * @param message 信息 + * @param user 用户 + */ + private void saveSuccess(String business, String message, UserDetails user) { + User raw = UserUtils.toUser(user); + Log log = new Log(); + log.setType(LogType.AUTHENTICATION); + log.setSignature(business); + log.setSuccess(true); + log.setBody("为了安全,本内容隐藏"); + log.setModule("登录模块"); + log.setBusiness(registry.text(business)); + log.setResponse(message); + log.setOperator(Optional.ofNullable(raw).map(Domain::getName).orElse("未知")); + log.setPeriod(0L); + log.setStartTime(new Date()); + // 写入日志 + logService.create(log); + } + + /** + * 保存失败日志 + * + * @param business 业务 + * @param message 消息 + * @param username 用户名 + * @param error 错误 + */ + private void saveError(String business, String message, String username, String error) { + Log log = new Log(); + log.setType(LogType.AUTHENTICATION); + log.setSignature(business); + log.setSuccess(false); + log.setBody("某人尝试使用用户名'" + username + "',密码:******,进行登录"); + log.setModule("登录模块"); + log.setBusiness(registry.text(business)); + log.setError(error); + log.setResponse(message); + log.setOperator(userService.findByUsername(username).map(Domain::getName).orElse("未知")); + log.setPeriod(0L); + log.setStartTime(new Date()); + // 写入日志 + logService.create(log); + } + +} diff --git a/flyfish-user/src/main/java/com/flyfish/framework/config/AuthenticationAuditorImpl.java b/flyfish-user/src/main/java/com/flyfish/framework/config/AuthenticationAuditorImpl.java new file mode 100644 index 0000000..53b6dea --- /dev/null +++ b/flyfish-user/src/main/java/com/flyfish/framework/config/AuthenticationAuditorImpl.java @@ -0,0 +1,82 @@ +package com.flyfish.framework.config; + +import com.flyfish.framework.domain.AdminUserDetails; +import com.flyfish.framework.domain.po.User; +import com.flyfish.framework.enums.UserStatus; +import com.flyfish.framework.service.AuthenticationAuditor; +import com.flyfish.framework.service.AuthenticationLogger; +import com.flyfish.framework.service.ReactiveUserService; +import com.flyfish.framework.transform.ResultDataTransformer; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.server.WebFilterExchange; +import reactor.core.publisher.Mono; + +import javax.annotation.Resource; +import java.util.Date; + +/** + * 提供对象写入,登录登出审计 + * + * @author wangyu + */ +public class AuthenticationAuditorImpl extends ResultDataTransformer implements AuthenticationAuditor { + + @Resource + private ReactiveUserService reactiveUserService; + @Resource + private AuthenticationLogger authenticationLogger; + + /** + * 检查用户异常,无异常,清空错误次数,设置上次登录 + * + * @param user 用户 + */ + @Override + public Mono success(UserDetails user) { + AdminUserDetails details = (AdminUserDetails) user; + User updating = new User(); + updating.setId(details.getId()); + updating.setErrorCount(0); + updating.setLastTime(new Date()); + return reactiveUserService.updateSelectiveById(updating) + .then(Mono.fromRunnable(() -> authenticationLogger.success(user))); + } + + /** + * 登录失败的回调 + * 当输入错误的账号密码,记录错误次数,否则只记录日志 + * + * @param webFilterExchange 请求信息 + * @param exception 异常 + */ + @Override + public Mono error(WebFilterExchange webFilterExchange, AuthenticationException exception) { + return webFilterExchange.getExchange() + .getFormData() + .flatMap(data -> { + // 当且仅当为凭据错误,尝试 + if (exception instanceof BadCredentialsException) { + return reactiveUserService.findByUsername(data.getFirst("username")) + .flatMap(user -> { + User updating = new User(); + updating.setId(user.getId()); + updating.setErrorCount(user.getErrorCount() + 1); + if (updating.getErrorCount() >= 5) { + updating.setStatus(UserStatus.LOCKED); + } + return reactiveUserService.updateSelectiveById(updating); + }) + .map(user -> data); + } + return Mono.just(data); + }) + .flatMap(data -> Mono.fromRunnable(() -> authenticationLogger.failure(data, exception))); + } + + @Override + public Mono logout(UserDetails userDetails) { + return Mono.fromRunnable(() -> authenticationLogger.logout(userDetails)); + } +} diff --git a/flyfish-user/src/main/java/com/flyfish/framework/config/WebSecurityConfig.java b/flyfish-user/src/main/java/com/flyfish/framework/config/WebSecurityConfig.java index ad21266..7e4aa69 100644 --- a/flyfish-user/src/main/java/com/flyfish/framework/config/WebSecurityConfig.java +++ b/flyfish-user/src/main/java/com/flyfish/framework/config/WebSecurityConfig.java @@ -12,8 +12,8 @@ import com.flyfish.framework.handler.JsonAuthenticationFailureHandler; import com.flyfish.framework.handler.JsonAuthenticationSuccessHandler; import com.flyfish.framework.handler.JsonLogoutSuccessHandler; import com.flyfish.framework.initializer.UserInitializer; +import com.flyfish.framework.service.AuthenticationAuditor; import com.flyfish.framework.service.UserService; -import com.flyfish.framework.transform.ResultDataTransformer; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -58,6 +58,11 @@ public class WebSecurityConfig { return delegatingPasswordEncoder; } + @Bean + public AuthenticationAuditor loginAuditor() { + return new AuthenticationAuditorImpl(); + } + @ConditionalOnProperty(value = "jwt.enable", havingValue = "true") @Bean("contextRepository") public JwtSecurityContextRepository jwtSecurityContextRepository() { @@ -75,11 +80,6 @@ public class WebSecurityConfig { return new TokenProvider(properties); } - @Bean - public ResultDataTransformer resultDataTransformer() { - return new ResultDataTransformer(); - } - /** * spring安全拦截规则配置 * @@ -90,10 +90,10 @@ public class WebSecurityConfig { @Bean @SuppressWarnings("all") public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, - ResultDataTransformer dataTransformer, TokenProvider tokenProvider, SecurityProperties properties, - ReactiveUserDetailsService userDetailsService) { + ReactiveUserDetailsService userDetailsService, + AuthenticationAuditor authenticationAuditor) { http .securityContextRepository(contextRepository()) .authorizeExchange() @@ -105,13 +105,13 @@ public class WebSecurityConfig { .formLogin() // 配置登录节点 .authenticationManager(authenticationManager(properties, userDetailsService)) .authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)) - .authenticationFailureHandler(new JsonAuthenticationFailureHandler(dataTransformer)) - .authenticationSuccessHandler(new JsonAuthenticationSuccessHandler(dataTransformer)) + .authenticationSuccessHandler(new JsonAuthenticationSuccessHandler(authenticationAuditor)) + .authenticationFailureHandler(new JsonAuthenticationFailureHandler(authenticationAuditor)) .requiresAuthenticationMatcher(pathMatchers(HttpMethod.POST, "/login", "/api/login")) .and() .logout() .logoutUrl("/api/logout") - .logoutSuccessHandler(new JsonLogoutSuccessHandler(dataTransformer, tokenProvider)) + .logoutSuccessHandler(new JsonLogoutSuccessHandler(authenticationAuditor, tokenProvider)) .and() .csrf().disable(); return http.build(); diff --git a/flyfish-user/src/main/java/com/flyfish/framework/domain/AdminUserDetails.java b/flyfish-user/src/main/java/com/flyfish/framework/domain/AdminUserDetails.java index f0d62ee..1dc7a11 100644 --- a/flyfish-user/src/main/java/com/flyfish/framework/domain/AdminUserDetails.java +++ b/flyfish-user/src/main/java/com/flyfish/framework/domain/AdminUserDetails.java @@ -138,7 +138,7 @@ public class AdminUserDetails implements UserDetails, IUser { @Override @JsonIgnore public boolean isCredentialsNonExpired() { - return isAccountNonExpired(); + return null == validDate || validDate.after(new Date()); } @JsonIgnore diff --git a/flyfish-user/src/main/java/com/flyfish/framework/service/MongoUserDetailsServiceImpl.java b/flyfish-user/src/main/java/com/flyfish/framework/service/MongoUserDetailsServiceImpl.java index 58971ab..4aef0f3 100644 --- a/flyfish-user/src/main/java/com/flyfish/framework/service/MongoUserDetailsServiceImpl.java +++ b/flyfish-user/src/main/java/com/flyfish/framework/service/MongoUserDetailsServiceImpl.java @@ -17,7 +17,6 @@ import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.server.context.ServerSecurityContextRepository; import org.springframework.stereotype.Service; import org.springframework.web.server.ServerWebExchange; @@ -39,9 +38,6 @@ import java.util.function.Supplier; @Service public class MongoUserDetailsServiceImpl implements MongoUserDetailsService { - // 登录失败限制 - private static final int ERROR_LIMIT = 5; - // 存储用户校验规则的map private static final Map, Supplier> checkMap; @@ -152,24 +148,4 @@ public class MongoUserDetailsServiceImpl implements MongoUserDetailsService { .flatMap(context -> contextRepository.save(exchange, context)); } - - /** - * 发生错误时的处理 - * - * @param exchange 交换 - * @return 结果 - */ - @Override - public Mono error(ServerWebExchange exchange) { - return exchange.getFormData().flatMap(data -> userService.findByUsername(data.getFirst("username"))) - .flatMap(user -> { - User updating = new User(); - updating.setId(user.getId()); - updating.setErrorCount(user.getErrorCount() + 1); - if (updating.getErrorCount() >= ERROR_LIMIT) { - updating.setStatus(UserStatus.LOCKED); - } - return userService.updateSelectiveById(updating); - }); - } } diff --git a/flyfish-user/src/main/java/com/flyfish/framework/service/UserService.java b/flyfish-user/src/main/java/com/flyfish/framework/service/UserService.java index be7e47d..a306faf 100644 --- a/flyfish-user/src/main/java/com/flyfish/framework/service/UserService.java +++ b/flyfish-user/src/main/java/com/flyfish/framework/service/UserService.java @@ -8,6 +8,9 @@ import com.flyfish.framework.service.impl.BaseServiceImpl; import com.flyfish.framework.utils.Assert; import com.flyfish.framework.utils.StrengthUtils; import org.apache.commons.lang3.StringUtils; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -16,7 +19,7 @@ import java.util.List; import java.util.Optional; @Service -public class UserService extends BaseServiceImpl { +public class UserService extends BaseServiceImpl implements UserFindService { @Resource private PasswordEncoder passwordEncoder; @@ -27,6 +30,7 @@ public class UserService extends BaseServiceImpl { * @param username 用户 * @return 结果 */ + @Override public Optional findByUsername(String username) { return ((UserRepository) repository).findByUsername(username); } @@ -61,4 +65,5 @@ public class UserService extends BaseServiceImpl { public List getList(Qo query) { return super.getList(query); } + } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationFailureHandler.java b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationFailureHandler.java index 9538181..b72403e 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationFailureHandler.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationFailureHandler.java @@ -1,20 +1,15 @@ package com.flyfish.framework.handler; import com.flyfish.framework.bean.Result; -import com.flyfish.framework.service.MongoUserDetailsService; -import com.flyfish.framework.transform.DataBufferTransformer; +import com.flyfish.framework.service.AuthenticationAuditor; +import com.flyfish.framework.utils.AuthenticationMessages; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; import reactor.core.publisher.Mono; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - /** * 基于json的登录失败包装,详见Spring Security * @@ -22,16 +17,11 @@ import java.util.Optional; */ public class JsonAuthenticationFailureHandler implements ServerAuthenticationFailureHandler { - private final String defaultMessage = "用户登录异常!"; // 数据块工厂 - private final DataBufferTransformer> dataBufferTransformer; + private final AuthenticationAuditor authenticationAuditor; - private final Map, String> descriptionMap = new HashMap<>(); - - public JsonAuthenticationFailureHandler(DataBufferTransformer> dataBufferTransformer) { - this.dataBufferTransformer = dataBufferTransformer; - descriptionMap.put(BadCredentialsException.class, "用户名密码不正确!"); - // todo 剩下的都在userDetailsService + public JsonAuthenticationFailureHandler(AuthenticationAuditor authenticationAuditor) { + this.authenticationAuditor = authenticationAuditor; } /** @@ -45,15 +35,8 @@ public class JsonAuthenticationFailureHandler implements ServerAuthenticationFai public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) { ServerHttpResponse response = webFilterExchange.getExchange().getResponse(); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); - return Mono.justOrEmpty(Optional.ofNullable(webFilterExchange.getExchange().getApplicationContext())) - .flatMap(applicationContext -> { - MongoUserDetailsService userDetailsService = applicationContext.getBean(MongoUserDetailsService.class); - if (exception instanceof BadCredentialsException) { - return userDetailsService.error(webFilterExchange.getExchange()) - .flatMap(user -> write(response, exception)); - } - return write(response, exception); - }); + // 记录错误次数,错误日志等,最后写入 + return authenticationAuditor.error(webFilterExchange, exception).then(write(response, exception)); } /** @@ -64,9 +47,6 @@ public class JsonAuthenticationFailureHandler implements ServerAuthenticationFai * @return 结果 */ private Mono write(ServerHttpResponse response, AuthenticationException exception) { - return response.writeWith(Mono.fromCallable(() -> - dataBufferTransformer.transform( - Result.error(descriptionMap.getOrDefault(exception.getClass(), exception.getMessage())) - ))); + return response.writeWith(authenticationAuditor.transform(Result.error(AuthenticationMessages.message(exception)))); } } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationSuccessHandler.java b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationSuccessHandler.java index bbb29fa..eab3a94 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationSuccessHandler.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationSuccessHandler.java @@ -1,13 +1,13 @@ package com.flyfish.framework.handler; import com.flyfish.framework.bean.Result; -import com.flyfish.framework.transform.DataBufferTransformer; +import com.flyfish.framework.service.AuthenticationAuditor; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; import reactor.core.publisher.Mono; @@ -21,7 +21,7 @@ import reactor.core.publisher.Mono; public class JsonAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler { // 数据块工厂 - private final DataBufferTransformer> dataBufferTransformer; + private final AuthenticationAuditor authenticationAuditor; /** * 登录成功后要返回用户的基本信息,节省带宽 @@ -35,9 +35,9 @@ public class JsonAuthenticationSuccessHandler implements ServerAuthenticationSuc ServerHttpResponse response = webFilterExchange.getExchange().getResponse(); HttpHeaders headers = response.getHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - // 查询 - return response.writeWith(ReactiveSecurityContextHolder.getContext().map(securityContext -> - dataBufferTransformer.transform(Result.accept(securityContext.getAuthentication().getPrincipal())) - )); + Object principal = authentication.getPrincipal(); + // 如果登录成功,清空错误次数,更新上次登录时间 + return authenticationAuditor.success((UserDetails) principal) + .then(response.writeWith(authenticationAuditor.transform(Result.accept(principal)))); } } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonLogoutSuccessHandler.java b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonLogoutSuccessHandler.java index 6d41ab8..4c57f04 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonLogoutSuccessHandler.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonLogoutSuccessHandler.java @@ -2,11 +2,12 @@ package com.flyfish.framework.handler; import com.flyfish.framework.bean.Result; import com.flyfish.framework.configuration.jwt.TokenProvider; -import com.flyfish.framework.transform.DataBufferTransformer; +import com.flyfish.framework.service.AuthenticationAuditor; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler; import reactor.core.publisher.Mono; @@ -20,8 +21,9 @@ import reactor.core.publisher.Mono; public class JsonLogoutSuccessHandler implements ServerLogoutSuccessHandler { // 数据块工厂 - private final DataBufferTransformer> dataBufferTransformer; + private final AuthenticationAuditor authenticationAuditor; + // token提供者 private final TokenProvider tokenProvider; @Override @@ -29,7 +31,12 @@ public class JsonLogoutSuccessHandler implements ServerLogoutSuccessHandler { ServerHttpResponse response = webFilterExchange.getExchange().getResponse(); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); tokenProvider.removeToken(webFilterExchange.getExchange()); - // 查询 - return response.writeWith(Mono.just(dataBufferTransformer.transform(Result.ok()))); + Object principal = authentication.getPrincipal(); + if ("anonymous".equals(principal)) { + return response.writeWith(authenticationAuditor.transform(Result.ok())); + } + // 记录日志 + return authenticationAuditor.logout((UserDetails) authentication.getPrincipal()) + .then(response.writeWith(authenticationAuditor.transform(Result.ok()))); } } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/service/AuthenticationAuditor.java b/flyfish-web/src/main/java/com/flyfish/framework/service/AuthenticationAuditor.java new file mode 100644 index 0000000..1e877d4 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/service/AuthenticationAuditor.java @@ -0,0 +1,38 @@ +package com.flyfish.framework.service; + +import com.flyfish.framework.bean.Result; +import com.flyfish.framework.transform.DataBufferTransformer; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.server.WebFilterExchange; +import reactor.core.publisher.Mono; + +/** + * 登录审计 + * + * @author wangyu + */ +public interface AuthenticationAuditor extends DataBufferTransformer> { + + /** + * 登录成功的回调 + * + * @param userDetails 用户信息 + */ + Mono success(UserDetails userDetails); + + /** + * 登录失败的回调 + * + * @param webFilterExchange 请求信息 + * @param exception 异常 + */ + Mono error(WebFilterExchange webFilterExchange, AuthenticationException exception); + + /** + * 退出登录的回调 + * + * @param userDetails 用户详情 + */ + Mono logout(UserDetails userDetails); +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/service/AuthenticationLogger.java b/flyfish-web/src/main/java/com/flyfish/framework/service/AuthenticationLogger.java new file mode 100644 index 0000000..808396b --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/service/AuthenticationLogger.java @@ -0,0 +1,36 @@ +package com.flyfish.framework.service; + +import com.flyfish.framework.domain.po.User; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.util.MultiValueMap; + +/** + * 认证日志者 + * + * @author wangyu + */ +public interface AuthenticationLogger { + + /** + * 记录成功 + * + * @param user 用户 + */ + void success(UserDetails user); + + /** + * 记录失败 + * + * @param form 表单 + * @param exception 错误异常 + */ + void failure(MultiValueMap form, AuthenticationException exception); + + /** + * 记录退出 + * + * @param user 用户 + */ + void logout(UserDetails user); +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/service/MongoUserDetailsService.java b/flyfish-web/src/main/java/com/flyfish/framework/service/MongoUserDetailsService.java index b85c6cf..1b95d10 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/service/MongoUserDetailsService.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/service/MongoUserDetailsService.java @@ -1,6 +1,5 @@ package com.flyfish.framework.service; -import com.flyfish.framework.domain.po.User; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; @@ -42,12 +41,4 @@ public interface MongoUserDetailsService extends ReactiveUserDetailsService, Rea * @return 结果 */ Mono logout(ServerWebExchange exchange); - - /** - * 发生错误时的处理 - * - * @param exchange 交换 - * @return 结果 - */ - Mono error(ServerWebExchange exchange); } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/service/UserFindService.java b/flyfish-web/src/main/java/com/flyfish/framework/service/UserFindService.java new file mode 100644 index 0000000..1a51eed --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/service/UserFindService.java @@ -0,0 +1,20 @@ +package com.flyfish.framework.service; + + +import com.flyfish.framework.domain.po.User; + +import java.util.Optional; + +/** + * 用户查找service + * @author wangyu + */ +public interface UserFindService { + + /** + * 通过用户名查找 + * @param username 用户名 + * @return 结果 + */ + Optional findByUsername(String username); +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/transform/DataBufferTransformer.java b/flyfish-web/src/main/java/com/flyfish/framework/transform/DataBufferTransformer.java index 3732ffa..8c28b3f 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/transform/DataBufferTransformer.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/transform/DataBufferTransformer.java @@ -1,13 +1,20 @@ package com.flyfish.framework.transform; import org.springframework.core.io.buffer.DataBuffer; +import reactor.core.publisher.Mono; /** * 用于转换DataBuffer的工具 * - * @auther wangyu + * @author wangyu */ public interface DataBufferTransformer { - DataBuffer transform(T source); + /** + * 写入二进制数据,返回数据 + * + * @param source 数据源 + * @return 结果 + */ + Mono transform(T source); } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/transform/ResultDataTransformer.java b/flyfish-web/src/main/java/com/flyfish/framework/transform/ResultDataTransformer.java index cd2b84b..0809a97 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/transform/ResultDataTransformer.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/transform/ResultDataTransformer.java @@ -4,6 +4,7 @@ import com.flyfish.framework.bean.Result; import com.flyfish.framework.utils.JacksonUtil; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; +import reactor.core.publisher.Mono; import java.nio.charset.Charset; @@ -21,10 +22,11 @@ public class ResultDataTransformer implements DataBufferTransformer> { } @Override - public DataBuffer transform(Result source) { + public Mono transform(Result source) { return JacksonUtil.toJson(source) .map(result -> result.getBytes(Charset.defaultCharset())) .map(factory::wrap) - .orElse(null); + .map(Mono::just) + .orElse(Mono.empty()); } } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/utils/AuthenticationMessages.java b/flyfish-web/src/main/java/com/flyfish/framework/utils/AuthenticationMessages.java new file mode 100644 index 0000000..032c80b --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/utils/AuthenticationMessages.java @@ -0,0 +1,29 @@ +package com.flyfish.framework.utils; + +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.AuthenticationException; + +import java.util.Map; + +/** + * 认证消息 + * @author wangyu + */ +public interface AuthenticationMessages { + + String DEFAULT_MESSAGE = "用户登录异常!"; + + // todo 剩下的都在userDetailsService + Map, String> DESCRIPTION_MAP = MapBuilder., String>builder() + .put(BadCredentialsException.class, "用户名密码不正确!") + .build(); + + /** + * 消息构建,通过异常 + * @param exception 异常 + * @return 结果 + */ + static String message(AuthenticationException exception) { + return DESCRIPTION_MAP.getOrDefault(exception.getClass(), exception.getMessage()); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/utils/UserUtils.java b/flyfish-web/src/main/java/com/flyfish/framework/utils/UserUtils.java index b7ed042..4c52d93 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/utils/UserUtils.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/utils/UserUtils.java @@ -3,6 +3,7 @@ package com.flyfish.framework.utils; import com.flyfish.framework.domain.base.IUser; import com.flyfish.framework.domain.po.User; import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.userdetails.UserDetails; /** * 用户工具 @@ -20,4 +21,20 @@ public class UserUtils { public static User extractUser(SecurityContext securityContext) { return ((IUser) securityContext.getAuthentication().getPrincipal()).toUser(); } + + /** + * 转换用户 + * + * @param userDetails 用户详情 + * @return 结果 + */ + public static User toUser(UserDetails userDetails) { + if (null == userDetails) { + return null; + } + if (userDetails instanceof IUser) { + return ((IUser) userDetails).toUser(); + } + return null; + } }