diff --git a/achobeta-infra-common/src/main/java/com/achobeta/www/common/entity/BaseAssignIDEntity.java b/achobeta-infra-common/src/main/java/com/achobeta/www/common/entity/BaseAssignIDEntity.java index b882f51..5c71f25 100644 --- a/achobeta-infra-common/src/main/java/com/achobeta/www/common/entity/BaseAssignIDEntity.java +++ b/achobeta-infra-common/src/main/java/com/achobeta/www/common/entity/BaseAssignIDEntity.java @@ -5,6 +5,7 @@ import lombok.Setter; import org.springframework.format.annotation.DateTimeFormat; +import java.io.Serializable; import java.time.LocalDateTime; /** @@ -18,12 +19,11 @@ */ @Getter @Setter -public class BaseAssignIDEntity { +public class BaseAssignIDEntity implements Serializable { /** * id, incr */ - @TableField("c_id") - @TableId(type = IdType.ASSIGN_ID) + @TableId(type = IdType.ASSIGN_ID, value = "c_id") private Long id; /** diff --git a/achobeta-infra-common/src/main/java/com/achobeta/www/common/entity/BaseIncrIDEntity.java b/achobeta-infra-common/src/main/java/com/achobeta/www/common/entity/BaseIncrIDEntity.java index d43f1da..e2e6885 100644 --- a/achobeta-infra-common/src/main/java/com/achobeta/www/common/entity/BaseIncrIDEntity.java +++ b/achobeta-infra-common/src/main/java/com/achobeta/www/common/entity/BaseIncrIDEntity.java @@ -5,6 +5,7 @@ import lombok.Setter; import org.springframework.format.annotation.DateTimeFormat; +import java.io.Serializable; import java.time.LocalDateTime; /** @@ -17,12 +18,11 @@ */ @Getter @Setter -public class BaseIncrIDEntity { +public class BaseIncrIDEntity implements Serializable { /** * id, incr */ - @TableField("c_id") - @TableId(type = IdType.AUTO) + @TableId(type = IdType.AUTO, value = "c_id") private Long id; /** diff --git a/achobeta-infra-oauth/pom.xml b/achobeta-infra-oauth/pom.xml index 40bf37d..8f29be4 100644 --- a/achobeta-infra-oauth/pom.xml +++ b/achobeta-infra-oauth/pom.xml @@ -16,9 +16,9 @@ - com.mysql - mysql-connector-j - runtime + com.achobeta.www + achobeta-infra-common + 0.0.1-SNAPSHOT org.springframework.boot @@ -39,6 +39,52 @@ logstash-logback-encoder 7.1.1 + + + org.springframework.boot + spring-boot-starter-security + + + com.baomidou + mybatis-plus-boot-starter + 3.5.3.1 + + + com.mysql + mysql-connector-j + runtime + + + cn.hutool + hutool-all + 5.7.21 + + + + com.alibaba.fastjson2 + fastjson2-extension-spring6 + ${fastjson.version} + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.apache.commons + commons-pool2 + + + org.springframework.session + spring-session-data-redis + 3.1.3 + + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + provided + @@ -47,6 +93,15 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + + 21 + 21 + --enable-preview + + diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/AchoBetaWebSecurityConfig.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/AchoBetaWebSecurityConfig.java new file mode 100644 index 0000000..6276b50 --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/AchoBetaWebSecurityConfig.java @@ -0,0 +1,72 @@ +package com.achobeta.www.oauth.config; + +import com.achobeta.www.oauth.config.handler.AuthenticationFailureHandler; +import com.achobeta.www.oauth.config.handler.logout.AuthenticationLogoutHandler; +import com.achobeta.www.oauth.config.handler.logout.AuthenticationLogoutSuccessHandler; +import com.achobeta.www.oauth.config.handler.AuthenticationSuccessHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.server.SecurityWebFilterChain; + +import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole; + +/** + * + * security config + * + * + * @author jettcc in 2023/10/18 + * @version 1.0 + */ +@Configuration +@EnableWebFluxSecurity +public class AchoBetaWebSecurityConfig { + @Autowired + private AuthenticationWhitelistConfig whitelistConfig; + @Bean + public SecurityWebFilterChain defaultSecurityFilterChain(ServerHttpSecurity http) { + String[] urls = whitelistConfig.getUrls().toArray(new String[0]); + http + .authorizeExchange((authorize) -> authorize + // 白名单路径 + .pathMatchers(urls) + .permitAll() + .pathMatchers("/admin/**") + .hasRole("ADMIN") + .pathMatchers("/db/**") + .access((authentication, context) -> + hasRole("ADMIN").check(authentication, context) + .filter(decision -> !decision.isGranted()) + .switchIfEmpty(hasRole("DBA").check(authentication, context)) + ) + .anyExchange().denyAll() + ).formLogin(fl -> + fl.authenticationSuccessHandler(new AuthenticationSuccessHandler()) + .authenticationFailureHandler(new AuthenticationFailureHandler())) + .logout(logoutSpec -> logoutSpec.logoutHandler(new AuthenticationLogoutHandler()) + .logoutSuccessHandler(new AuthenticationLogoutSuccessHandler()) + ) +// .httpBasic(basicSpec -> { +// basicSpec. +// }) + ; + + + http.csrf(ServerHttpSecurity.CsrfSpec::disable); + return http.build(); + } + + /** + * this bean is encryptor + */ + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} + diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/AuthenticationWhitelistConfig.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/AuthenticationWhitelistConfig.java new file mode 100644 index 0000000..c335936 --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/AuthenticationWhitelistConfig.java @@ -0,0 +1,24 @@ +package com.achobeta.www.oauth.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * + * Whitelist request URL list + * + * + * @author jettcc in 2023/10/31 + * @version 1.0 + */ +@Getter +@Setter +@Component +@ConfigurationProperties(prefix = "achobeta.auth.whitelist") +public class AuthenticationWhitelistConfig { + private List urls; +} diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/MybatisFillConfig.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/MybatisFillConfig.java new file mode 100644 index 0000000..f5b0908 --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/MybatisFillConfig.java @@ -0,0 +1,34 @@ +package com.achobeta.www.oauth.config; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * + * Mybatis fill config + * + * + * @author jettcc in 2023/10/18 + * @version 1.0 + */ +@Component +@EnableTransactionManagement +public class MybatisFillConfig implements MetaObjectHandler { + @Override + public void insertFill(MetaObject metaObject) { + this.strictInsertFill(metaObject, "uuid", String.class, UUID.randomUUID().toString()); + this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); + this.strictInsertFill(metaObject, "version", Integer.class, 1); + this.strictInsertFill(metaObject, "deleted", Integer.class, 0); + } + + @Override + public void updateFill(MetaObject metaObject) { + this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + } +} \ No newline at end of file diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/RedisConfig.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/RedisConfig.java new file mode 100644 index 0000000..a8e9ecd --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/RedisConfig.java @@ -0,0 +1,37 @@ +package com.achobeta.www.oauth.config; + +import com.alibaba.fastjson2.support.spring6.data.redis.GenericFastJsonRedisSerializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; + +/** + * + * redis config + * + * + * @author jettcc in 2023/10/31 + * @version 1.0 + */ +@Configuration +public class RedisConfig { + @Bean + public RedisSerializer springSessionDefaultRedisSerializer() { + return new GenericFastJsonRedisSerializer(); + } + + @Bean + public RedisTemplate redisTemplate(LettuceConnectionFactory factory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(factory); + redisTemplate.setKeySerializer(RedisSerializer.string()); + redisTemplate.setValueSerializer(RedisSerializer.json()); + redisTemplate.setHashKeySerializer(RedisSerializer.string()); + redisTemplate.setHashValueSerializer(RedisSerializer.json()); + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } + +} \ No newline at end of file diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/SpringSessionConfig.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/SpringSessionConfig.java new file mode 100644 index 0000000..c9c1036 --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/SpringSessionConfig.java @@ -0,0 +1,25 @@ +package com.achobeta.www.oauth.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.session.SaveMode; +import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession; + +/** + * + * spring session config + * + * + * @author jettcc in 2023/11/1 + * @version 1.0 + */ +@Configuration(proxyBeanMethods = false) +@EnableRedisWebSession(redisNamespace = "achobeta:infra", saveMode = SaveMode.ALWAYS) +public class SpringSessionConfig { + @Bean + public LettuceConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(); + } + +} diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/AuthenticationFailureHandler.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/AuthenticationFailureHandler.java new file mode 100644 index 0000000..e18b8b6 --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/AuthenticationFailureHandler.java @@ -0,0 +1,26 @@ +package com.achobeta.www.oauth.config.handler; + +import com.achobeta.www.common.util.GlobalServiceStatusCode; +import org.springframework.http.server.reactive.ServerHttpResponse; +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 static com.achobeta.www.oauth.utils.ResponseUtil.createAccessDeniedResponse; + +/** + * + * handler authentication failure + * + * + * @author jettcc in 2023/10/23 + * @version 1.0 + */ +public class AuthenticationFailureHandler implements ServerAuthenticationFailureHandler { + @Override + public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) { + ServerHttpResponse response = webFilterExchange.getExchange().getResponse(); + return createAccessDeniedResponse(response, GlobalServiceStatusCode.USER_NO_PERMISSION); + } +} diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/AuthenticationSuccessHandler.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/AuthenticationSuccessHandler.java new file mode 100644 index 0000000..ed6e858 --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/AuthenticationSuccessHandler.java @@ -0,0 +1,31 @@ +package com.achobeta.www.oauth.config.handler; + +import com.achobeta.www.common.util.GlobalServiceStatusCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.server.WebFilterExchange; +import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; +import reactor.core.publisher.Mono; + +import static com.achobeta.www.oauth.utils.ResponseUtil.createAccessDeniedResponse; + +/** + * + * authentication success handler + * + * + * @author jettcc in 2023/10/23 + * @version 1.0 + */ +@Slf4j +public class AuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler { + + @Override + public Mono onAuthenticationSuccess(WebFilterExchange webFilterExchange, + Authentication authentication) { + ServerHttpResponse response = webFilterExchange.getExchange().getResponse(); + return createAccessDeniedResponse(response, GlobalServiceStatusCode.SYSTEM_SUCCESS); + } + +} diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/logout/AuthenticationLogoutHandler.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/logout/AuthenticationLogoutHandler.java new file mode 100644 index 0000000..ca15aad --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/logout/AuthenticationLogoutHandler.java @@ -0,0 +1,39 @@ +package com.achobeta.www.oauth.config.handler.logout; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.server.WebFilterExchange; +import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler; +import reactor.core.publisher.Mono; + +/** + * + * authentication logout handler, logoutHandler -> logout success + * + * + * @author jettcc in 2023/10/23 + * @version 1.0 + */ +@Configuration +@Slf4j +public class AuthenticationLogoutHandler implements ServerLogoutHandler { + @Override + public Mono logout(WebFilterExchange exchange, Authentication authentication) { + log.info("logout handler"); + return exchange.getExchange().getSession().flatMap( + webSession -> { + // cancel authentication + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(null); + // clear context + SecurityContextHolder.clearContext(); + return webSession.invalidate(); + } + ) + .contextWrite(ReactiveSecurityContextHolder.clearContext()); + } +} \ No newline at end of file diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/logout/AuthenticationLogoutSuccessHandler.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/logout/AuthenticationLogoutSuccessHandler.java new file mode 100644 index 0000000..e17e91e --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/config/handler/logout/AuthenticationLogoutSuccessHandler.java @@ -0,0 +1,28 @@ +package com.achobeta.www.oauth.config.handler.logout; + +import com.achobeta.www.common.util.GlobalServiceStatusCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.server.WebFilterExchange; +import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler; +import reactor.core.publisher.Mono; + +import static com.achobeta.www.oauth.utils.ResponseUtil.createAccessDeniedResponse; +/** + * + * handler logout success logic + * + * + * @author jettcc in 2023/10/23 + * @version 1.0 + */ +@Slf4j +public class AuthenticationLogoutSuccessHandler implements ServerLogoutSuccessHandler { + @Override + public Mono onLogoutSuccess(WebFilterExchange webFilterExchange, Authentication authentication) { + log.info(" logout success"); + ServerHttpResponse response = webFilterExchange.getExchange().getResponse(); + return createAccessDeniedResponse(response, GlobalServiceStatusCode.SYSTEM_SUCCESS, "logout success"); + } +} diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/dao/AuthUserMapper.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/dao/AuthUserMapper.java new file mode 100644 index 0000000..a57c4ba --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/dao/AuthUserMapper.java @@ -0,0 +1,17 @@ +package com.achobeta.www.oauth.dao; + +import com.achobeta.www.oauth.entity.AuthUser; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * + * user dao + * + * + * @author jettcc in 2023/10/18 + * @version 1.0 + */ +@Mapper +public interface AuthUserMapper extends BaseMapper { +} diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/entity/AchobetaUserDetails.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/entity/AchobetaUserDetails.java new file mode 100644 index 0000000..f1daab9 --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/entity/AchobetaUserDetails.java @@ -0,0 +1,75 @@ +package com.achobeta.www.oauth.entity; + +import cn.hutool.core.lang.Assert; +import lombok.*; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.io.Serial; +import java.io.Serializable; +import java.util.*; +/** + * + * user details + * + * + * @author jettcc in 2023/10/17 + * @version 1.0 + */ +@Getter +@Setter +public class AchobetaUserDetails implements UserDetails { + private Long id; + private String uuid; + private String password; + private final String username; + private final String phone; + private final Set authorities; + private final boolean accountNonExpired; + private final boolean accountNonLocked; + private final boolean credentialsNonExpired; + private final boolean enabled; + + public AchobetaUserDetails(Long id, String uuid, + String username, String password, + + String phone, + List authorities, + boolean accountNonExpired, + boolean accountNonLocked, + boolean credentialsNonExpired, + boolean enabled) { + this.uuid = uuid; + this.id = id; + this.password = password; + this.phone = phone; + this.username = username; + this.accountNonExpired = accountNonExpired; + this.accountNonLocked = accountNonLocked; + this.credentialsNonExpired = credentialsNonExpired; + this.enabled = enabled; + this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities)); // 非空判断+排序 + } + + private static SortedSet sortAuthorities(Collection authorities) { + Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection"); + SortedSet sortedAuthorities = new TreeSet<>(new AchobetaUserDetails.AuthorityComparator()); + for (GrantedAuthority grantedAuthority : authorities) { + Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements"); + sortedAuthorities.add(grantedAuthority); + } + return sortedAuthorities; + } + + private static class AuthorityComparator implements Comparator, Serializable { + @Serial + private static final long serialVersionUID = 600L; + public int compare(GrantedAuthority g1, GrantedAuthority g2) { + if (g2.getAuthority() == null) { + return -1; + } else { + return g1.getAuthority() == null ? 1 : g1.getAuthority().compareTo(g2.getAuthority()); + } + } + } +} diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/entity/AuthUser.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/entity/AuthUser.java new file mode 100644 index 0000000..cf62ed6 --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/entity/AuthUser.java @@ -0,0 +1,57 @@ +package com.achobeta.www.oauth.entity; + +import com.achobeta.www.common.entity.BaseIncrIDEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serial; + +/** + * + * auth user model + * + * + * @author jettcc in 2023/10/17 + * @version 1.0 + */ +@TableName(value = "t_ab_auth_user", autoResultMap = true) +@Getter +@Setter +@Builder +public class AuthUser extends BaseIncrIDEntity { + @Serial + private static final long serialVersionUID = 1L; + + @TableField(value = "c_uuid", fill = FieldFill.INSERT) + private String uuid; + + @TableField("c_username") + private String username; + + @TableField("c_password") + private String password; + + @TableField("c_account") + private String account; + + @TableField("c_phone") + private String phone; + + @TableField("c_email") + private String email; + + @TableField("c_openid") + private String openid; + + @TableField("c_enabled") + private Boolean enabled; + + @TableField("c_is_expired") + private Boolean expired; + + @TableField("c_is_locked") + private Boolean locked; + +} diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/security/AchobetaUserDetailsServiceImpl.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/security/AchobetaUserDetailsServiceImpl.java new file mode 100644 index 0000000..3a20cb9 --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/security/AchobetaUserDetailsServiceImpl.java @@ -0,0 +1,59 @@ +package com.achobeta.www.oauth.security; + +import com.achobeta.www.oauth.dao.AuthUserMapper; +import com.achobeta.www.oauth.entity.AchobetaUserDetails; +import com.achobeta.www.oauth.entity.AuthUser; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +import java.util.Optional; + +import static java.lang.StringTemplate.STR; + +/** + * + * user details service + * + * + * @author jettcc in 2023/10/17 + * @version 1.0 + */ +@Slf4j +@Service +public class AchobetaUserDetailsServiceImpl implements ReactiveUserDetailsService { + + private final AuthUserMapper authUserMapper; + + public AchobetaUserDetailsServiceImpl(AuthUserMapper authUserMapper) { + this.authUserMapper = authUserMapper; + } + + @Override + public Mono findByUsername(String username) { + log.info(STR. "account login username: \{ username } " ); + var user = Optional.ofNullable(authUserMapper.selectOne(new QueryWrapper() + .eq("c_username", username))) + .orElseThrow(() -> new UsernameNotFoundException(STR. "account \{ username } not found" )); + var authList = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); + + return Mono.just(new AchobetaUserDetails( + user.getId(), + user.getUuid(), + user.getUsername(), + user.getPassword(), + user.getPhone(), + authList, + !user.getExpired(), + !user.getLocked(), + // need to use session center to judge + true, + user.getEnabled() + )); + } +} diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/utils/KVRedisManger.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/utils/KVRedisManger.java new file mode 100644 index 0000000..c57ff6e --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/utils/KVRedisManger.java @@ -0,0 +1,91 @@ +package com.achobeta.www.oauth.utils; + + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * + * Encapsulate Redis operations + * + * + * @author jettcc in 2023/10/31 + * @version 1.0 + */ +@Component +@Slf4j +public class KVRedisManger { + private static final String TAG = "KVRedisManger"; + + private final RedisTemplate kv; + + public KVRedisManger(RedisTemplate kv) { + this.kv = kv; + } + + // =============================common============================ + + /** + * specify cache expiration time + * + * @param key key + * @param time time(second) + */ + public boolean expire(String key, long time) { + try { + if (time > 0) { + kv.expire(key, time, TimeUnit.SECONDS); + } + return true; + } catch (Exception e) { + log.error(STR."[\{TAG}] >> redis expire error, msg: \{e.getMessage()}"); + return false; + } + } + + /** + * get expiration time + * @param key key not null + * @return Time (seconds) Returns 0 for permanent validity + */ + public long getExpireTime(String key) { + return Optional.ofNullable(kv.getExpire(key, TimeUnit.SECONDS)) + .orElse(-1L); + } + + /** + * determine if the key exists. + * @param key key + */ + public boolean hasKey(String key) { + try { + return Boolean.TRUE.equals(kv.hasKey(key)); + } catch (Exception e) { + return false; + } + } + + /** + * delete the specified key. + * @param key It can be one or more. + */ + + public void del(String... key) { + if (key != null && key.length > 0) { + kv.delete(List.of(key)); + } + } + + + /* + String related methods + */ + + +} diff --git a/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/utils/ResponseUtil.java b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/utils/ResponseUtil.java new file mode 100644 index 0000000..3ff3906 --- /dev/null +++ b/achobeta-infra-oauth/src/main/java/com/achobeta/www/oauth/utils/ResponseUtil.java @@ -0,0 +1,48 @@ +package com.achobeta.www.oauth.utils; + +import cn.hutool.json.JSONUtil; +import com.achobeta.www.common.util.GlobalServiceStatusCode; +import com.achobeta.www.common.util.SystemJsonResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpResponse; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; + +/** + * + * authentication response util + * + * + * @author jettcc in 2023/10/23 + * @version 1.0 + */ +public class ResponseUtil { + /** + * 构造webflux返回时的结构 + */ + public static Mono createAccessDeniedResponse(ServerHttpResponse resp, GlobalServiceStatusCode code) { + return createResponse(resp, code, null); + } + + public static Mono createAccessDeniedResponse(ServerHttpResponse resp, GlobalServiceStatusCode code, String msg) { + return createResponse(resp, code, msg); + } + + private static Mono createResponse(ServerHttpResponse resp, GlobalServiceStatusCode code, String msg) { + resp.setStatusCode(HttpStatus.OK); + resp.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + return resp.writeWith(Mono.just(resp.bufferFactory() + .wrap(JSONUtil.toJsonStr(buildResponseMessage(code, msg)) + .getBytes(StandardCharsets.UTF_8)))); + } + + private static SystemJsonResponse buildResponseMessage(GlobalServiceStatusCode code, String msg) { + if (code == GlobalServiceStatusCode.SYSTEM_SUCCESS) { + return SystemJsonResponse.SYSTEM_SUCCESS(msg); + } + return SystemJsonResponse.CUSTOMIZE_ERROR(code); + } +} diff --git a/achobeta-infra-oauth/src/main/resources/application.yml b/achobeta-infra-oauth/src/main/resources/application.yml index c9c7d42..ee5071f 100644 --- a/achobeta-infra-oauth/src/main/resources/application.yml +++ b/achobeta-infra-oauth/src/main/resources/application.yml @@ -1,7 +1,18 @@ spring: + security: + user: + name: "achobeta" + # 123123 + password: "$2a$10$FzwjxqvdR0c9/ScWHnJVMeSQYU/4xazCKVrz/.3/zhps6GtYlI966" profiles: # debug in local, use application-local.yaml default: local + datasource: + url: jdbc:mysql://rm-wz9i5gw9n7zm8mj4hro.mysql.rds.aliyuncs.com:3306/achobeta_infra?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai + username: + password: + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: com.mysql.cj.jdbc.Driver main: # specify the type of gateway application # case 'reactive' : processing requests in non-blocking mode @@ -9,14 +20,34 @@ spring: web-application-type: reactive application: name: achobeta-infra-oauth - - - - - - - - + data: + # redis + redis: + database: 5 + host: localhost + port: 6379 + timeout: 3000ms + lettuce: + pool: + # Maximum number of connections, a negative value indicates no limit, default is 8 + max-active: 20 + # Maximum blocking wait time, a negative value indicates no limit, default is -1 + max-wait: -1 + # Maximum idle connections, default is 8 + max-idle: 8 + # Minimum idle connections, default is 0 + min-idle: 0 +achobeta: + auth: + whitelist: + urls: + - "/login" + - "/v0/**" + - "/auth/oauth/token" + - "/swagger**/**" + - "/webjars/**" + - "/doc.html" + - "/*/*/api-docs" # ---------------------------- Try not to modify the following configurations ---------------------------- # Default port number, not to change it, diff --git a/achobeta-infra-oauth/src/test/java/com/achobeta/www/oauth/AchoBetaAuthUserTest.java b/achobeta-infra-oauth/src/test/java/com/achobeta/www/oauth/AchoBetaAuthUserTest.java new file mode 100644 index 0000000..dd1d39b --- /dev/null +++ b/achobeta-infra-oauth/src/test/java/com/achobeta/www/oauth/AchoBetaAuthUserTest.java @@ -0,0 +1,22 @@ +package com.achobeta.www.oauth; + +import com.achobeta.www.oauth.dao.AuthUserMapper; +import com.achobeta.www.oauth.entity.AuthUser; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@SpringBootTest +public class AchoBetaAuthUserTest { + @Autowired + private AuthUserMapper authUserMapper; + @Test + public void mockUser() { + AuthUser jettcc = AuthUser.builder() + .username("jettcc") + .password(new BCryptPasswordEncoder().encode("123123")) + .phone("13712345678").build(); + authUserMapper.insert(jettcc); + } +}