Merge remote-tracking branch 'origin/master'

# Conflicts:
#	flyfish-user/src/main/java/com/flyfish/framework/service/MongoUserDetailsServiceImpl.java
This commit is contained in:
王瑜 2021-01-25 13:44:58 +08:00
commit 76d9ed3f0a
23 changed files with 430 additions and 91 deletions

View File

@ -96,6 +96,11 @@ public class User extends AuditDomain implements IUser {
*/ */
private Integer errorCount = 0; private Integer errorCount = 0;
/**
* 上次登录日期
*/
private Date lastTime;
@Override @Override
public User toUser() { public User toUser() {
return this; return this;

View File

@ -1,6 +1,5 @@
package com.flyfish.framework.logging.config; package com.flyfish.framework.logging.config;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import java.util.HashMap; import java.util.HashMap;
@ -27,6 +26,8 @@ public class LoggingTextRegistry implements InitializingBean {
this.mapping.put("UPDATE_ALL", "更新全部"); this.mapping.put("UPDATE_ALL", "更新全部");
this.mapping.put("DELETE", "删除"); this.mapping.put("DELETE", "删除");
this.mapping.put("SYNC", "同步"); this.mapping.put("SYNC", "同步");
this.mapping.put("LOGIN", "系统登录");
this.mapping.put("LOGOUT", "退出登录");
} }
public String text(String code) { public String text(String code) {

View File

@ -15,6 +15,9 @@ import java.util.Date;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Log extends Domain { public class Log extends Domain {
// 日志类型
private LogType type;
// 业务 // 业务
private String business; private String business;

View File

@ -5,6 +5,7 @@ import com.flyfish.framework.domain.base.BaseQo;
import com.flyfish.framework.domain.base.NameLikeQo; import com.flyfish.framework.domain.base.NameLikeQo;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.springframework.data.domain.Sort;
import java.util.List; import java.util.List;
@ -25,11 +26,18 @@ public class LogQo extends NameLikeQo<Log> {
private List<String> range; private List<String> range;
private String type;
@Override @Override
public CriteriaBuilder<Log> criteriaBuilder() { public CriteriaBuilder<Log> criteriaBuilder() {
return super.criteriaBuilder() return super.criteriaBuilder()
.with("operator", CriteriaBuilder.Builders.LIKE) .with("operator", CriteriaBuilder.Builders.LIKE)
.with("module", "success") .with("type", "module", "success")
.with("range", "startTime", CriteriaBuilder.Builders.DATE_RANGE); .with("range", "startTime", CriteriaBuilder.Builders.DATE_RANGE);
} }
@Override
public Sort sorts() {
return Sort.by(Sort.Order.desc("startTime"));
}
} }

View File

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

View File

@ -3,6 +3,7 @@ package com.flyfish.framework.logging.service;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.flyfish.framework.logging.config.LoggingTextRegistry; import com.flyfish.framework.logging.config.LoggingTextRegistry;
import com.flyfish.framework.logging.domain.Log; import com.flyfish.framework.logging.domain.Log;
import com.flyfish.framework.logging.domain.LogType;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -38,6 +39,7 @@ public class LogManager implements DisposableBean {
executorService.execute(() -> { executorService.execute(() -> {
// 构建日志 // 构建日志
Log log = new Log(); Log log = new Log();
log.setType(LogType.OPERATION);
log.setSuccess(context.isSuccess()); log.setSuccess(context.isSuccess());
log.setBody(bodyString(context.getArgs())); log.setBody(bodyString(context.getArgs()));
log.setModule(context.getModule()); log.setModule(context.getModule());

View File

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

View File

@ -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<Void> 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<Void> 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<Void> logout(UserDetails userDetails) {
return Mono.fromRunnable(() -> authenticationLogger.logout(userDetails));
}
}

View File

@ -12,8 +12,8 @@ import com.flyfish.framework.handler.JsonAuthenticationFailureHandler;
import com.flyfish.framework.handler.JsonAuthenticationSuccessHandler; import com.flyfish.framework.handler.JsonAuthenticationSuccessHandler;
import com.flyfish.framework.handler.JsonLogoutSuccessHandler; import com.flyfish.framework.handler.JsonLogoutSuccessHandler;
import com.flyfish.framework.initializer.UserInitializer; import com.flyfish.framework.initializer.UserInitializer;
import com.flyfish.framework.service.AuthenticationAuditor;
import com.flyfish.framework.service.UserService; 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.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -58,6 +58,11 @@ public class WebSecurityConfig {
return delegatingPasswordEncoder; return delegatingPasswordEncoder;
} }
@Bean
public AuthenticationAuditor loginAuditor() {
return new AuthenticationAuditorImpl();
}
@ConditionalOnProperty(value = "jwt.enable", havingValue = "true") @ConditionalOnProperty(value = "jwt.enable", havingValue = "true")
@Bean("contextRepository") @Bean("contextRepository")
public JwtSecurityContextRepository jwtSecurityContextRepository() { public JwtSecurityContextRepository jwtSecurityContextRepository() {
@ -75,11 +80,6 @@ public class WebSecurityConfig {
return new TokenProvider(properties); return new TokenProvider(properties);
} }
@Bean
public ResultDataTransformer resultDataTransformer() {
return new ResultDataTransformer();
}
/** /**
* spring安全拦截规则配置 * spring安全拦截规则配置
* *
@ -90,10 +90,10 @@ public class WebSecurityConfig {
@Bean @Bean
@SuppressWarnings("all") @SuppressWarnings("all")
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
ResultDataTransformer dataTransformer,
TokenProvider tokenProvider, TokenProvider tokenProvider,
SecurityProperties properties, SecurityProperties properties,
ReactiveUserDetailsService userDetailsService) { ReactiveUserDetailsService userDetailsService,
AuthenticationAuditor authenticationAuditor) {
http http
.securityContextRepository(contextRepository()) .securityContextRepository(contextRepository())
.authorizeExchange() .authorizeExchange()
@ -105,13 +105,13 @@ public class WebSecurityConfig {
.formLogin() // 配置登录节点 .formLogin() // 配置登录节点
.authenticationManager(authenticationManager(properties, userDetailsService)) .authenticationManager(authenticationManager(properties, userDetailsService))
.authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)) .authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))
.authenticationFailureHandler(new JsonAuthenticationFailureHandler(dataTransformer)) .authenticationSuccessHandler(new JsonAuthenticationSuccessHandler(authenticationAuditor))
.authenticationSuccessHandler(new JsonAuthenticationSuccessHandler(dataTransformer)) .authenticationFailureHandler(new JsonAuthenticationFailureHandler(authenticationAuditor))
.requiresAuthenticationMatcher(pathMatchers(HttpMethod.POST, "/login", "/api/login")) .requiresAuthenticationMatcher(pathMatchers(HttpMethod.POST, "/login", "/api/login"))
.and() .and()
.logout() .logout()
.logoutUrl("/api/logout") .logoutUrl("/api/logout")
.logoutSuccessHandler(new JsonLogoutSuccessHandler(dataTransformer, tokenProvider)) .logoutSuccessHandler(new JsonLogoutSuccessHandler(authenticationAuditor, tokenProvider))
.and() .and()
.csrf().disable(); .csrf().disable();
return http.build(); return http.build();

View File

@ -138,7 +138,7 @@ public class AdminUserDetails implements UserDetails, IUser {
@Override @Override
@JsonIgnore @JsonIgnore
public boolean isCredentialsNonExpired() { public boolean isCredentialsNonExpired() {
return isAccountNonExpired(); return null == validDate || validDate.after(new Date());
} }
@JsonIgnore @JsonIgnore

View File

@ -17,7 +17,6 @@ import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.NoOpPasswordEncoder; 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.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
@ -39,9 +38,6 @@ import java.util.function.Supplier;
@Service @Service
public class MongoUserDetailsServiceImpl implements MongoUserDetailsService { public class MongoUserDetailsServiceImpl implements MongoUserDetailsService {
// 登录失败限制
private static final int ERROR_LIMIT = 5;
// 存储用户校验规则的map // 存储用户校验规则的map
private static final Map<Function<User, Boolean>, Supplier<AuthenticationException>> checkMap; private static final Map<Function<User, Boolean>, Supplier<AuthenticationException>> checkMap;
@ -152,24 +148,4 @@ public class MongoUserDetailsServiceImpl implements MongoUserDetailsService {
.flatMap(context -> contextRepository.save(exchange, context)); .flatMap(context -> contextRepository.save(exchange, context));
} }
/**
* 发生错误时的处理
*
* @param exchange 交换
* @return 结果
*/
@Override
public Mono<User> 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);
});
}
} }

View File

@ -8,6 +8,9 @@ import com.flyfish.framework.service.impl.BaseServiceImpl;
import com.flyfish.framework.utils.Assert; import com.flyfish.framework.utils.Assert;
import com.flyfish.framework.utils.StrengthUtils; import com.flyfish.framework.utils.StrengthUtils;
import org.apache.commons.lang3.StringUtils; 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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -16,7 +19,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
@Service @Service
public class UserService extends BaseServiceImpl<User> { public class UserService extends BaseServiceImpl<User> implements UserFindService {
@Resource @Resource
private PasswordEncoder passwordEncoder; private PasswordEncoder passwordEncoder;
@ -27,6 +30,7 @@ public class UserService extends BaseServiceImpl<User> {
* @param username 用户 * @param username 用户
* @return 结果 * @return 结果
*/ */
@Override
public Optional<User> findByUsername(String username) { public Optional<User> findByUsername(String username) {
return ((UserRepository) repository).findByUsername(username); return ((UserRepository) repository).findByUsername(username);
} }
@ -61,4 +65,5 @@ public class UserService extends BaseServiceImpl<User> {
public List<User> getList(Qo<User> query) { public List<User> getList(Qo<User> query) {
return super.getList(query); return super.getList(query);
} }
} }

View File

@ -1,20 +1,15 @@
package com.flyfish.framework.handler; package com.flyfish.framework.handler;
import com.flyfish.framework.bean.Result; import com.flyfish.framework.bean.Result;
import com.flyfish.framework.service.MongoUserDetailsService; import com.flyfish.framework.service.AuthenticationAuditor;
import com.flyfish.framework.transform.DataBufferTransformer; import com.flyfish.framework.utils.AuthenticationMessages;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/** /**
* 基于json的登录失败包装详见Spring Security * 基于json的登录失败包装详见Spring Security
* *
@ -22,16 +17,11 @@ import java.util.Optional;
*/ */
public class JsonAuthenticationFailureHandler implements ServerAuthenticationFailureHandler { public class JsonAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {
private final String defaultMessage = "用户登录异常!";
// 数据块工厂 // 数据块工厂
private final DataBufferTransformer<Result<?>> dataBufferTransformer; private final AuthenticationAuditor authenticationAuditor;
private final Map<Class<? extends AuthenticationException>, String> descriptionMap = new HashMap<>(); public JsonAuthenticationFailureHandler(AuthenticationAuditor authenticationAuditor) {
this.authenticationAuditor = authenticationAuditor;
public JsonAuthenticationFailureHandler(DataBufferTransformer<Result<?>> dataBufferTransformer) {
this.dataBufferTransformer = dataBufferTransformer;
descriptionMap.put(BadCredentialsException.class, "用户名密码不正确!");
// todo 剩下的都在userDetailsService
} }
/** /**
@ -45,15 +35,8 @@ public class JsonAuthenticationFailureHandler implements ServerAuthenticationFai
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) { public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
ServerHttpResponse response = webFilterExchange.getExchange().getResponse(); ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
response.getHeaders().setContentType(MediaType.APPLICATION_JSON); response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return Mono.justOrEmpty(Optional.ofNullable(webFilterExchange.getExchange().getApplicationContext())) // 记录错误次数错误日志等最后写入
.flatMap(applicationContext -> { return authenticationAuditor.error(webFilterExchange, exception).then(write(response, exception));
MongoUserDetailsService userDetailsService = applicationContext.getBean(MongoUserDetailsService.class);
if (exception instanceof BadCredentialsException) {
return userDetailsService.error(webFilterExchange.getExchange())
.flatMap(user -> write(response, exception));
}
return write(response, exception);
});
} }
/** /**
@ -64,9 +47,6 @@ public class JsonAuthenticationFailureHandler implements ServerAuthenticationFai
* @return 结果 * @return 结果
*/ */
private Mono<Void> write(ServerHttpResponse response, AuthenticationException exception) { private Mono<Void> write(ServerHttpResponse response, AuthenticationException exception) {
return response.writeWith(Mono.fromCallable(() -> return response.writeWith(authenticationAuditor.transform(Result.error(AuthenticationMessages.message(exception))));
dataBufferTransformer.transform(
Result.error(descriptionMap.getOrDefault(exception.getClass(), exception.getMessage()))
)));
} }
} }

View File

@ -1,13 +1,13 @@
package com.flyfish.framework.handler; package com.flyfish.framework.handler;
import com.flyfish.framework.bean.Result; import com.flyfish.framework.bean.Result;
import com.flyfish.framework.transform.DataBufferTransformer; import com.flyfish.framework.service.AuthenticationAuditor;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.Authentication; 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.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -21,7 +21,7 @@ import reactor.core.publisher.Mono;
public class JsonAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler { public class JsonAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
// 数据块工厂 // 数据块工厂
private final DataBufferTransformer<Result<?>> dataBufferTransformer; private final AuthenticationAuditor authenticationAuditor;
/** /**
* 登录成功后要返回用户的基本信息节省带宽 * 登录成功后要返回用户的基本信息节省带宽
@ -35,9 +35,9 @@ public class JsonAuthenticationSuccessHandler implements ServerAuthenticationSuc
ServerHttpResponse response = webFilterExchange.getExchange().getResponse(); ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
HttpHeaders headers = response.getHeaders(); HttpHeaders headers = response.getHeaders();
headers.setContentType(MediaType.APPLICATION_JSON); headers.setContentType(MediaType.APPLICATION_JSON);
// 查询 Object principal = authentication.getPrincipal();
return response.writeWith(ReactiveSecurityContextHolder.getContext().map(securityContext -> // 如果登录成功清空错误次数更新上次登录时间
dataBufferTransformer.transform(Result.accept(securityContext.getAuthentication().getPrincipal())) return authenticationAuditor.success((UserDetails) principal)
)); .then(response.writeWith(authenticationAuditor.transform(Result.accept(principal))));
} }
} }

View File

@ -2,11 +2,12 @@ package com.flyfish.framework.handler;
import com.flyfish.framework.bean.Result; import com.flyfish.framework.bean.Result;
import com.flyfish.framework.configuration.jwt.TokenProvider; import com.flyfish.framework.configuration.jwt.TokenProvider;
import com.flyfish.framework.transform.DataBufferTransformer; import com.flyfish.framework.service.AuthenticationAuditor;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.Authentication; 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.WebFilterExchange;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler; import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -20,8 +21,9 @@ import reactor.core.publisher.Mono;
public class JsonLogoutSuccessHandler implements ServerLogoutSuccessHandler { public class JsonLogoutSuccessHandler implements ServerLogoutSuccessHandler {
// 数据块工厂 // 数据块工厂
private final DataBufferTransformer<Result<?>> dataBufferTransformer; private final AuthenticationAuditor authenticationAuditor;
// token提供者
private final TokenProvider tokenProvider; private final TokenProvider tokenProvider;
@Override @Override
@ -29,7 +31,12 @@ public class JsonLogoutSuccessHandler implements ServerLogoutSuccessHandler {
ServerHttpResponse response = webFilterExchange.getExchange().getResponse(); ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
response.getHeaders().setContentType(MediaType.APPLICATION_JSON); response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
tokenProvider.removeToken(webFilterExchange.getExchange()); tokenProvider.removeToken(webFilterExchange.getExchange());
// 查询 Object principal = authentication.getPrincipal();
return response.writeWith(Mono.just(dataBufferTransformer.transform(Result.ok()))); if ("anonymous".equals(principal)) {
return response.writeWith(authenticationAuditor.transform(Result.ok()));
}
// 记录日志
return authenticationAuditor.logout((UserDetails) authentication.getPrincipal())
.then(response.writeWith(authenticationAuditor.transform(Result.ok())));
} }
} }

View File

@ -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<Result<?>> {
/**
* 登录成功的回调
*
* @param userDetails 用户信息
*/
Mono<Void> success(UserDetails userDetails);
/**
* 登录失败的回调
*
* @param webFilterExchange 请求信息
* @param exception 异常
*/
Mono<Void> error(WebFilterExchange webFilterExchange, AuthenticationException exception);
/**
* 退出登录的回调
*
* @param userDetails 用户详情
*/
Mono<Void> logout(UserDetails userDetails);
}

View File

@ -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<String, String> form, AuthenticationException exception);
/**
* 记录退出
*
* @param user 用户
*/
void logout(UserDetails user);
}

View File

@ -1,6 +1,5 @@
package com.flyfish.framework.service; package com.flyfish.framework.service;
import com.flyfish.framework.domain.po.User;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
@ -42,12 +41,4 @@ public interface MongoUserDetailsService extends ReactiveUserDetailsService, Rea
* @return 结果 * @return 结果
*/ */
Mono<Void> logout(ServerWebExchange exchange); Mono<Void> logout(ServerWebExchange exchange);
/**
* 发生错误时的处理
*
* @param exchange 交换
* @return 结果
*/
Mono<User> error(ServerWebExchange exchange);
} }

View File

@ -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<User> findByUsername(String username);
}

View File

@ -1,13 +1,20 @@
package com.flyfish.framework.transform; package com.flyfish.framework.transform;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import reactor.core.publisher.Mono;
/** /**
* 用于转换DataBuffer的工具 * 用于转换DataBuffer的工具
* *
* @auther wangyu * @author wangyu
*/ */
public interface DataBufferTransformer<T> { public interface DataBufferTransformer<T> {
DataBuffer transform(T source); /**
* 写入二进制数据返回数据
*
* @param source 数据源
* @return 结果
*/
Mono<DataBuffer> transform(T source);
} }

View File

@ -4,6 +4,7 @@ import com.flyfish.framework.bean.Result;
import com.flyfish.framework.utils.JacksonUtil; import com.flyfish.framework.utils.JacksonUtil;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.DataBufferFactory;
import reactor.core.publisher.Mono;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -21,10 +22,11 @@ public class ResultDataTransformer implements DataBufferTransformer<Result<?>> {
} }
@Override @Override
public DataBuffer transform(Result<?> source) { public Mono<DataBuffer> transform(Result<?> source) {
return JacksonUtil.toJson(source) return JacksonUtil.toJson(source)
.map(result -> result.getBytes(Charset.defaultCharset())) .map(result -> result.getBytes(Charset.defaultCharset()))
.map(factory::wrap) .map(factory::wrap)
.orElse(null); .map(Mono::just)
.orElse(Mono.empty());
} }
} }

View File

@ -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<Class<? extends AuthenticationException>, String> DESCRIPTION_MAP = MapBuilder.<Class<? extends AuthenticationException>, String>builder()
.put(BadCredentialsException.class, "用户名密码不正确!")
.build();
/**
* 消息构建通过异常
* @param exception 异常
* @return 结果
*/
static String message(AuthenticationException exception) {
return DESCRIPTION_MAP.getOrDefault(exception.getClass(), exception.getMessage());
}
}

View File

@ -3,6 +3,7 @@ package com.flyfish.framework.utils;
import com.flyfish.framework.domain.base.IUser; import com.flyfish.framework.domain.base.IUser;
import com.flyfish.framework.domain.po.User; import com.flyfish.framework.domain.po.User;
import org.springframework.security.core.context.SecurityContext; 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) { public static User extractUser(SecurityContext securityContext) {
return ((IUser) securityContext.getAuthentication().getPrincipal()).toUser(); 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;
}
} }