提交 aa031f46 编写于 作者: 有来技术

feat:webflux集成kaptcha验证码

上级 d86a9d54
......@@ -29,22 +29,40 @@ public interface MemberFeignClient {
@GetMapping("/app-api/v1/members/detail/{id}")
Result<UmsMember> getUserEntityById(@PathVariable Long id);
/**
* 获取认证会员信息
*/
@GetMapping("/app-api/v1/members/openid/{openid}")
Result<MemberAuthDTO> loadUserByOpenId(@PathVariable String openid);
/**
* 扣减会员余额
*/
@PutMapping("/app-api/v1/members/current/balances/_deduct")
<T> Result<T> deductBalance( @RequestParam Long balances);
<T> Result<T> deductBalance(@RequestParam Long balances);
/**
* 添加浏览记录
*/
@PostMapping("/app-api/v1/members/view/history")
<T> Result<T> addProductViewHistory(@RequestBody ProductHistoryVO product);
/**
* 根据openId获取会员认证信息
*
* @param openid
* @return
*/
@GetMapping("/app-api/v1/members/openid/{openid}")
Result<MemberAuthDTO> loadUserByOpenId(@PathVariable String openid);
/**
* 根据手机号获取会员认证信息
*
* @param mobile
* @return
*/
@GetMapping("/app-api/v1/members/mobile/{mobile}")
Result<MemberAuthDTO> loadUserByMobile(String mobile);
}
......@@ -8,6 +8,6 @@ import lombok.Data;
public class MemberAuthDTO {
private Long userId;
private String openId;
private String username;
private Integer status;
}
......@@ -61,20 +61,6 @@ public class MemberController {
return Result.success(user);
}
@ApiOperation(value = "根据openid获取会员认证信息")
@ApiImplicitParam(name = "openid", value = "微信身份唯一标识", required = true, paramType = "path", dataType = "String")
@GetMapping("/openid/{openid}")
public Result<MemberAuthDTO> getByOpenid(@PathVariable String openid) {
UmsMember member = iUmsMemberService.getOne(new LambdaQueryWrapper<UmsMember>().eq(UmsMember::getOpenid, openid)
.select(UmsMember::getId,UmsMember::getOpenid,UmsMember::getStatus)
);
if (member == null) {
return Result.failed(ResultCode.USER_NOT_EXIST);
}
// 会员认证信息
MemberAuthDTO memberAuth = new MemberAuthDTO(member.getId(),member.getOpenid(),member.getStatus());
return Result.success(memberAuth);
}
@ApiOperation(value = "新增会员")
@ApiImplicitParam(name = "member", value = "实体JSON对象", required = true, paramType = "body", dataType = "UmsMember")
......@@ -153,4 +139,36 @@ public class MemberController {
return Result.success(Collections.emptySet());
}
}
@ApiOperation(value = "根据openid获取会员认证信息")
@ApiImplicitParam(name = "openid", value = "微信身份唯一标识", required = true, paramType = "path", dataType = "String")
@GetMapping("/openid/{openid}")
public Result<MemberAuthDTO> getByOpenid(@PathVariable String openid) {
UmsMember member = iUmsMemberService.getOne(new LambdaQueryWrapper<UmsMember>()
.eq(UmsMember::getOpenid, openid)
.select(UmsMember::getId, UmsMember::getOpenid, UmsMember::getStatus)
);
if (member == null) {
return Result.failed(ResultCode.USER_NOT_EXIST);
}
MemberAuthDTO memberAuth = new MemberAuthDTO(member.getId(), member.getOpenid(), member.getStatus());
return Result.success(memberAuth);
}
@ApiOperation(value = "根据手机号获取会员认证信息")
@ApiImplicitParam(name = "mobile", value = "会员手机号码", required = true, paramType = "path", dataType = "String")
@GetMapping("/mobile/{mobile}")
public Result<MemberAuthDTO> getByMobile(@PathVariable String mobile) {
UmsMember member = iUmsMemberService.getOne(new LambdaQueryWrapper<UmsMember>()
.eq(UmsMember::getMobile, mobile)
.select(UmsMember::getId, UmsMember::getMobile, UmsMember::getStatus)
);
if (member == null) {
return Result.failed(ResultCode.USER_NOT_EXIST);
}
MemberAuthDTO memberAuth = new MemberAuthDTO(member.getId(), member.getMobile(), member.getStatus());
return Result.success(memberAuth);
}
}
......@@ -56,7 +56,7 @@
<swagger.version>1.6.2</swagger.version>
<docker.image.prefix>youlai</docker.image.prefix>
<dockerfile-maven-plugin.version>1.4.13</dockerfile-maven-plugin.version>
<kaptcha.version>2.3.2</kaptcha.version>
</properties>
<dependencies>
......@@ -263,6 +263,12 @@
<artifactId>common-log</artifactId>
<version>${youlai.version}</version>
</dependency>
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
......@@ -38,4 +38,6 @@ public class UserAuthDTO {
*/
private List<String> roles;
}
......@@ -102,6 +102,7 @@
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
......
......@@ -4,8 +4,12 @@ package com.youlai.auth.common.constant;
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/9/30
*/
public interface OAuthConstants {
public interface AuthConstants {
String TEST_CLIENT_ID = "test";
String ADMIN_CLIENT_ID = "mall-admin";
String APP_CLIENT_ID = "mall-app";
}
package com.youlai.auth.common.jwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.stereotype.Component;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.util.Map;
import java.util.Set;
/**
* 描述: [类型描述]
* 创建时间: 2021-06-08
*
* @author hxr
* @version 1.0.0
* @update [序号][日期YYYY-MM-DD] [更改人姓名][变更描述]
*/
@Component
public class JwtGenerator {
@Autowired
private KeyPair keyPair;
public String createAccessToken(Set<String> authorities, Map<String, String> additional) {
String payload = new JwtPayloadBuilder()
.exp(12 * 3600) // 默认12小时
.authorities(authorities)
.additional(additional)
.builder();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RsaSigner signer = new RsaSigner(privateKey);
String accessToken = JwtHelper.encode(payload, signer).getEncoded();
return accessToken;
}
}
package com.youlai.auth.common.jwt;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* 描述: [类型描述]
* 创建时间: 2021/6/8
* @author hxr
* @version 1.0.0
* @update [序号][日期YYYY-MM-DD] [更改人姓名][变更描述]
*/
public class JwtPayloadBuilder {
private Map<String, Object> payload = new HashMap<>();
/**
* jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
**/
private String jti = IdUtil.simpleUUID();
/**
* jwt的签发时间
**/
private LocalDateTime iat = LocalDateTime.now();
/**
* jwt的过期时间,这个过期时间必须要大于签发时间
**/
private LocalDateTime exp;
/**
* 权限集
*/
private Set<String> authorities = new HashSet<>();
/**
* 附加的属性
*/
private Map<String, String> additional;
public JwtPayloadBuilder exp(int seconds) {
this.exp = this.iat.plusSeconds(seconds);
return this;
}
public JwtPayloadBuilder authorities(Set<String> authorities) {
this.authorities = authorities;
return this;
}
public JwtPayloadBuilder additional(Map<String, String> additional) {
this.additional = additional;
return this;
}
public String builder() {
payload.put("jti", jti);
payload.put("iat", this.iat.toEpochSecond(ZoneOffset.of("+8")));
payload.put("exp", this.exp.toEpochSecond(ZoneOffset.of("+8")));
if (CollectionUtil.isNotEmpty(additional)) {
payload.putAll(additional);
}
payload.put("authorities", this.authorities.toArray());
return JSONUtil.toJsonStr(payload);
}
}
......@@ -4,10 +4,10 @@ import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.youlai.auth.common.constant.OAuthConstants;
import com.youlai.common.constant.AuthConstants;
import com.youlai.auth.common.constant.AuthConstants;
import com.youlai.common.result.Result;
import com.youlai.common.web.util.JwtUtils;
import com.youlai.common.web.util.RequestUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
......@@ -60,7 +60,7 @@ public class OAuthController {
* 方式一:client_id、client_secret放在请求路径中(注:当前版本已废弃)
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
*/
String clientId = JwtUtils.getOAuth2ClientId();
String clientId = RequestUtils.getOAuth2ClientId();
log.info("OAuth认证授权 客户端ID:{},请求参数:{}", clientId, JSONUtil.toJsonStr(parameters));
/**
......@@ -69,7 +69,7 @@ public class OAuthController {
* 请求头自动填充,token必须原生返回,否则显示 undefined undefined
* 账号/密码: client_id/client_secret : client/123456
*/
if (OAuthConstants.TEST_CLIENT_ID.equals(clientId)) {
if (AuthConstants.TEST_CLIENT_ID.equals(clientId)) {
return tokenEndpoint.postAccessToken(principal, parameters).getBody();
}
......@@ -81,15 +81,15 @@ public class OAuthController {
@DeleteMapping("/logout")
public Result logout() {
JSONObject payload = JwtUtils.getJwtPayload();
String jti = payload.getStr(AuthConstants.JWT_JTI); // JWT唯一标识
Long expireTime = payload.getLong(AuthConstants.JWT_EXP); // JWT过期时间戳(单位:秒)
String jti = payload.getStr(com.youlai.common.constant.AuthConstants.JWT_JTI); // JWT唯一标识
Long expireTime = payload.getLong(com.youlai.common.constant.AuthConstants.JWT_EXP); // JWT过期时间戳(单位:秒)
if (expireTime != null) {
long currentTime = System.currentTimeMillis() / 1000;// 当前时间(单位:秒)
if (expireTime > currentTime) { // token未过期,添加至缓存作为黑名单限制访问,缓存时间为token过期剩余时间
redisTemplate.opsForValue().set(AuthConstants.TOKEN_BLACKLIST_PREFIX + jti, null, (expireTime - currentTime), TimeUnit.SECONDS);
redisTemplate.opsForValue().set(com.youlai.common.constant.AuthConstants.TOKEN_BLACKLIST_PREFIX + jti, null, (expireTime - currentTime), TimeUnit.SECONDS);
}
} else { // token 永不过期则永久加入黑名单
redisTemplate.opsForValue().set(AuthConstants.TOKEN_BLACKLIST_PREFIX + jti, null);
redisTemplate.opsForValue().set(com.youlai.common.constant.AuthConstants.TOKEN_BLACKLIST_PREFIX + jti, null);
}
return Result.success("注销成功");
}
......
package com.youlai.auth.security.config;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.youlai.auth.common.constant.AuthConstants;
import com.youlai.auth.security.core.clientdetails.ClientDetailsServiceImpl;
import com.youlai.auth.security.core.userdetails.member.MemberUserDetails;
import com.youlai.auth.security.core.userdetails.member.MemberUserDetailsServiceImpl;
import com.youlai.auth.security.core.userdetails.system.SysUserDetails;
import com.youlai.auth.security.core.userdetails.system.SysUserDetailsServiceImpl;
import com.youlai.auth.security.extension.wechat.WechatTokenGranter;
import com.youlai.auth.security.core.CustomUserDetailsByNameServiceWrapper;
import com.youlai.auth.security.core.userdetails.user.SysUserDetails;
import com.youlai.auth.security.core.userdetails.user.SysUserDetailsServiceImpl;
import com.youlai.auth.security.extension.memeber.wechat.WechatTokenGranter;
import com.youlai.auth.security.extension.PreAuthenticatedUserDetailsService;
import com.youlai.common.result.Result;
import com.youlai.common.result.ResultCode;
import lombok.RequiredArgsConstructor;
......@@ -53,8 +55,6 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
private final SysUserDetailsServiceImpl sysUserDetailsService;
private final MemberUserDetailsServiceImpl memberUserDetailsService;
private final List<UserDetailsService> userDetailsServices;
/**
* OAuth2客户端
*/
......@@ -87,10 +87,11 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
.accessTokenConverter(jwtAccessTokenConverter())
.tokenEnhancer(tokenEnhancerChain)
.tokenGranter(compositeTokenGranter)
// .userDetailsService(sysUserDetailsService)
// refresh token有两种使用方式:重复使用(true)、非重复使用(false),默认为true
// 1 重复使用:access token过期刷新时, refresh token过期时间未改变,仍以初次生成的时间为准
// 2 非重复使用:access token过期刷新时, refresh token过期时间延续,在refresh token有效期内刷新便永不失效达到无需再次登录的目的
//.userDetailsService(sysUserDetailsService)
/** refresh token有两种使用方式:重复使用(true)、非重复使用(false),默认为true
* 1 重复使用:access token过期刷新时, refresh token过期时间未改变,仍以初次生成的时间为准
* 2 非重复使用:access token过期刷新时, refresh token过期时间延续,在refresh token有效期内刷新便永不失效达到无需再次登录的目的
*/
.reuseRefreshTokens(true)
.tokenServices(tokenServices(endpoints)); // 自定义的TokenService
}
......@@ -111,13 +112,21 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
tokenServices.setTokenEnhancer(tokenEnhancerChain);
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new CustomUserDetailsByNameServiceWrapper<>(userDetailsServices));
provider.setPreAuthenticatedUserDetailsService(new PreAuthenticatedUserDetailsService<>(refreshTokenUserDetailsServiceMap()));
tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));
return tokenServices;
}
public Map<String, UserDetailsService> refreshTokenUserDetailsServiceMap() {
Map<String, UserDetailsService> clientUserDetailsServiceMap = new HashMap<>();
clientUserDetailsServiceMap.put(AuthConstants.ADMIN_CLIENT_ID, sysUserDetailsService);
clientUserDetailsServiceMap.put(AuthConstants.APP_CLIENT_ID, memberUserDetailsService);
return clientUserDetailsServiceMap;
}
/**
* 使用非对称加密算法对token签名
*/
......@@ -150,10 +159,16 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
SysUserDetails sysUserDetails = (SysUserDetails) principal;
additionalInfo.put("userId", sysUserDetails.getUserId());
additionalInfo.put("username", sysUserDetails.getUsername());
if (StrUtil.isNotBlank(sysUserDetails.getAuthenticationMethod())) {
additionalInfo.put("authenticationMethod", sysUserDetails.getAuthenticationMethod());
}
} else if (principal instanceof MemberUserDetails) {
MemberUserDetails memberUserDetails = (MemberUserDetails) principal;
additionalInfo.put("userId", memberUserDetails.getUserId());
additionalInfo.put("username", memberUserDetails.getUsername());
if (StrUtil.isNotBlank(memberUserDetails.getAuthenticationMethod())) {
additionalInfo.put("authenticationMethod", memberUserDetails.getAuthenticationMethod());
}
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
......
package com.youlai.auth.security.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import com.youlai.auth.security.core.userdetails.member.MemberUserDetailsServiceImpl;
import com.youlai.auth.security.core.userdetails.system.SysUserDetailsServiceImpl;
import com.youlai.auth.security.extension.wechat.WechatAuthenticationProvider;
import com.youlai.auth.security.extension.memeber.wechat.WechatAuthenticationProvider;
import com.youlai.mall.ums.api.MemberFeignClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
......
......@@ -21,9 +21,14 @@ import java.util.HashSet;
public class MemberUserDetails implements UserDetails {
private Long userId;
private String openId;
private String username;
private Boolean enabled;
/**
* 认证方式
*/
private String authenticationMethod;
/**
* 小程序会员用户体系
......@@ -32,7 +37,7 @@ public class MemberUserDetails implements UserDetails {
*/
public MemberUserDetails(MemberAuthDTO member) {
this.setUserId(member.getUserId());
this.setOpenId(member.getOpenId());
this.setUsername(member.getUsername());
this.setEnabled(GlobalConstants.STATUS_YES.equals(member.getStatus()));
}
......@@ -49,7 +54,7 @@ public class MemberUserDetails implements UserDetails {
@Override
public String getUsername() {
return this.openId;
return this.username;
}
@Override
......
package com.youlai.auth.security.core.userdetails.member;
import com.youlai.common.enums.AuthenticationMethodEnum;
import com.youlai.common.result.Result;
import com.youlai.common.result.ResultCode;
import com.youlai.mall.ums.api.MemberFeignClient;
import com.youlai.mall.ums.pojo.dto.MemberAuthDTO;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.DisabledException;
......@@ -16,7 +16,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* 系统用户体系业务类
* 系统管理用户
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
*/
......@@ -27,15 +27,20 @@ public class MemberUserDetailsServiceImpl implements UserDetailsService {
private final MemberFeignClient memberFeignClient;
@SneakyThrows
@Override
public UserDetails loadUserByUsername(String openid) throws UsernameNotFoundException {
public UserDetails loadUserByUsername(String username) {
return null;
}
public UserDetails loadUserByMobile(String mobile) {
MemberUserDetails userDetails = null;
Result<MemberAuthDTO> result = memberFeignClient.loadUserByOpenId(openid);
Result<MemberAuthDTO> result = memberFeignClient.loadUserByMobile(mobile);
if (Result.isSuccess(result)) {
MemberAuthDTO member = result.getData();
if (null != member) {
userDetails = new MemberUserDetails(member);
userDetails.setAuthenticationMethod(AuthenticationMethodEnum.MOBILE.getValue()); // 认证方式:OpenId
}
}
if (userDetails == null) {
......@@ -50,4 +55,26 @@ public class MemberUserDetailsServiceImpl implements UserDetailsService {
return userDetails;
}
public UserDetails loadUserByOpenId(String openId) {
MemberUserDetails userDetails = null;
Result<MemberAuthDTO> result = memberFeignClient.loadUserByOpenId(openId);
if (Result.isSuccess(result)) {
MemberAuthDTO member = result.getData();
if (null != member) {
userDetails = new MemberUserDetails(member);
userDetails.setAuthenticationMethod(AuthenticationMethodEnum.OPENID.getValue()); // 认证方式:OpenId
}
}
if (userDetails == null) {
throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg());
} else if (!userDetails.isEnabled()) {
throw new DisabledException("该账户已被禁用!");
} else if (!userDetails.isAccountNonLocked()) {
throw new LockedException("该账号已被锁定!");
} else if (!userDetails.isAccountNonExpired()) {
throw new AccountExpiredException("该账号已过期!");
}
return userDetails;
}
}
package com.youlai.auth.security.core.userdetails.system;
package com.youlai.auth.security.core.userdetails.user;
import cn.hutool.core.collection.CollectionUtil;
import com.youlai.admin.pojo.dto.UserAuthDTO;
......@@ -14,7 +14,7 @@ import java.util.Collection;
/**
* 用户认证信息
* 系统管理用户认证信息
*
* @author <a href="mailto:xianrui0365@163.com">haoxianrui</a>
* @date 2021/9/27
......@@ -26,6 +26,7 @@ public class SysUserDetails implements UserDetails {
* 扩展字段
*/
private Long userId;
private String authenticationMethod;
/**
* 默认字段
......@@ -36,9 +37,7 @@ public class SysUserDetails implements UserDetails {
private Collection<SimpleGrantedAuthority> authorities;
/**
* 系统管理用户体系
*
* @param user 系统管理用户认证信息
* 系统管理用户
*/
public SysUserDetails(UserAuthDTO user) {
this.setUserId(user.getUserId());
......
package com.youlai.auth.security.core.userdetails.system;
package com.youlai.auth.security.core.userdetails.user;
import com.youlai.admin.api.UserFeignClient;
import com.youlai.admin.pojo.dto.UserAuthDTO;
......
package com.youlai.auth.security.core;
package com.youlai.auth.security.extension;
import com.youlai.auth.common.constant.AuthConstants;
import com.youlai.auth.security.core.userdetails.member.MemberUserDetailsServiceImpl;
import com.youlai.auth.security.core.userdetails.system.SysUserDetailsServiceImpl;
import com.youlai.auth.security.extension.wechat.WechatAuthenticationToken;
import com.youlai.common.web.util.JwtUtils;
import com.youlai.common.enums.AuthenticationMethodEnum;
import com.youlai.common.web.util.RequestUtils;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.core.Authentication;
......@@ -11,51 +11,51 @@ import org.springframework.security.core.userdetails.AuthenticationUserDetailsSe
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.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.util.Assert;
import java.util.List;
import java.util.Map;
/**
* UserDetailsService 工厂类
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/10/2
*/
@NoArgsConstructor
public class CustomUserDetailsByNameServiceWrapper<T extends Authentication> implements AuthenticationUserDetailsService<T>, InitializingBean {
public class PreAuthenticatedUserDetailsService<T extends Authentication> implements AuthenticationUserDetailsService<T>, InitializingBean {
private Map<String, UserDetailsService> userDetailsServiceMap;
private List<UserDetailsService> userDetailsServiceList;
public CustomUserDetailsByNameServiceWrapper(List<UserDetailsService> userDetailsServiceList) {
Assert.notNull(userDetailsServiceList, "userDetailsService cannot be null.");
this.userDetailsServiceList = userDetailsServiceList;
public PreAuthenticatedUserDetailsService(Map<String, UserDetailsService> userDetailsServiceMap) {
Assert.notNull(userDetailsServiceMap, "userDetailsService cannot be null.");
this.userDetailsServiceMap = userDetailsServiceMap;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.userDetailsServiceList, "UserDetailsService must be set");
Assert.notNull(this.userDetailsServiceMap, "UserDetailsService must be set");
}
@Override
public UserDetails loadUserDetails(T authentication) throws UsernameNotFoundException {
UserDetailsService userDetailsService = null;
String clientId = JwtUtils.getOAuth2ClientId();
for (UserDetailsService detailsService : userDetailsServiceList) {
if (clientId.equals("youlai-mall-weapp")) {
if (detailsService instanceof MemberUserDetailsServiceImpl) {
userDetailsService = detailsService;
continue;
}
}else{
if (detailsService instanceof SysUserDetailsServiceImpl) {
userDetailsService = detailsService;
continue;
}
String clientId = RequestUtils.getOAuth2ClientId();
AuthenticationMethodEnum authenticationMethodEnum = AuthenticationMethodEnum.getByValue(RequestUtils.getAuthenticationMethod());
UserDetailsService userDetailsService = userDetailsServiceMap.get(clientId);
if (clientId.equals(AuthConstants.APP_CLIENT_ID)) {
MemberUserDetailsServiceImpl memberUserDetailsService = (MemberUserDetailsServiceImpl) userDetailsService;
switch (authenticationMethodEnum) {
case OPENID:
return memberUserDetailsService.loadUserByOpenId(authentication.getName());
default:
return memberUserDetailsService.loadUserByUsername(authentication.getName());
}
} else if (clientId.equals(AuthConstants.ADMIN_CLIENT_ID)) {
switch (authenticationMethodEnum) {
default:
return userDetailsService.loadUserByUsername(authentication.getName());
}
} else {
return userDetailsService.loadUserByUsername(authentication.getName());
}
return userDetailsService.loadUserByUsername(authentication.getName());
}
}
package com.youlai.auth.security.extension.wechat;
package com.youlai.auth.security.extension.memeber.wechat;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.bean.BeanUtil;
import com.youlai.auth.security.core.userdetails.member.MemberUserDetailsServiceImpl;
import com.youlai.common.result.Result;
import com.youlai.common.result.ResultCode;
import com.youlai.mall.ums.api.MemberFeignClient;
import com.youlai.mall.ums.pojo.dto.MemberAuthDTO;
import com.youlai.mall.ums.pojo.entity.UmsMember;
import lombok.Data;
import lombok.SneakyThrows;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
......@@ -27,12 +27,9 @@ import java.util.HashSet;
public class WechatAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
private WxMaService wxMaService;
private MemberFeignClient memberFeignClient;
/**
* 认证验证
*
......@@ -62,7 +59,7 @@ public class WechatAuthenticationProvider implements AuthenticationProvider {
memberFeignClient.add(member);
}
UserDetails userDetails = userDetailsService.loadUserByUsername(openid);
UserDetails userDetails = ((MemberUserDetailsServiceImpl)userDetailsService).loadUserByOpenId(openid);
WechatAuthenticationToken result = new WechatAuthenticationToken(userDetails, new HashSet<>());
result.setDetails(authentication.getDetails());
return result;
......
package com.youlai.auth.security.extension.wechat;
package com.youlai.auth.security.extension.memeber.wechat;
import lombok.Getter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
......
package com.youlai.auth.security.extension.wechat;
package com.youlai.auth.security.extension.memeber.wechat;
import cn.hutool.json.JSONUtil;
import org.springframework.security.authentication.*;
......
package com.youlai.auth.security.extension.wechat;
package com.youlai.auth.security.extension.memeber.wechat;
import lombok.Data;
/**
* 微信用户信息
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/9/29
* @date 2021/10/4
*/
@Data
public class WechatUserInfo {
private String avatarUrl;
private String city;
......
......@@ -67,5 +67,10 @@ public interface AuthConstants {
*/
String SAVE_MENU_PATH = "/youlai-admin/api/v1/menus";
/**
* 认证方式
*/
String AUTHENTICATION_METHOD = "authenticationMethod";
}
package com.youlai.common.enums;
import lombok.Getter;
/**
* 认证方式枚举
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/10/4
*/
public enum AuthenticationMethodEnum {
USERNAME("username", "用户名"),
MOBILE("mobile", "手机号"),
OPENID("openId", "开放式认证系统唯一身份标识");
@Getter
private String value;
@Getter
private String label;
AuthenticationMethodEnum(String value, String label) {
this.value = value;
this.label = label;
}
public static AuthenticationMethodEnum getByValue(String value) {
AuthenticationMethodEnum authenticationMethodEnum = null;
for (AuthenticationMethodEnum item : values()) {
if (item.getValue().equals(value)) {
authenticationMethodEnum = item;
continue;
}
}
return authenticationMethodEnum;
}
}
......@@ -27,11 +27,13 @@ public enum BusinessTypeEnum {
}
public static BusinessTypeEnum getValue(String code) {
BusinessTypeEnum businessTypeEnum=null;
for (BusinessTypeEnum value : values()) {
if (value.getCode().equals(code)) {
return value;
businessTypeEnum =value;
continue;
}
}
return null;
return businessTypeEnum;
}
}
......@@ -58,6 +58,11 @@
<artifactId>ip2region</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
</dependencies>
</project>
......@@ -55,36 +55,7 @@ public class JwtUtils {
return username;
}
/**
* 获取登录认证的客户端ID
*
* 兼容两种方式获取OAuth2客户端信息(client_id、client_secret)
* 方式一:client_id、client_secret放在请求路径中
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
*
* @return
*/
@SneakyThrows
public static String getOAuth2ClientId() {
String clientId;
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 从请求路径中获取
clientId = request.getParameter(AuthConstants.CLIENT_ID_KEY);
if (StrUtil.isNotBlank(clientId)) {
return clientId;
}
// 从请求头获取
String basic = request.getHeader(AuthConstants.AUTHORIZATION_KEY);
if (StrUtil.isNotBlank(basic) && basic.startsWith(AuthConstants.BASIC_PREFIX)) {
basic = basic.replace(AuthConstants.BASIC_PREFIX, Strings.EMPTY);
String basicPlainText = new String(Base64.getDecoder().decode(basic.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
clientId = basicPlainText.split(":")[0]; //client:secret
}
return clientId;
}
/**
* JWT获取用户角色列表
......
package com.youlai.common.web.util;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.JWSObject;
import com.youlai.common.constant.AuthConstants;
import com.youlai.common.enums.AuthenticationMethodEnum;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* 请求工具类
*
* @author xianrui
*/
@Slf4j
public class RequestUtils {
@SneakyThrows
public static String getGrantType() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String grantType = request.getParameter(AuthConstants.GRANT_TYPE_KEY);
return grantType;
}
/**
* 获取登录认证的客户端ID
* <p>
* 兼容两种方式获取OAuth2客户端信息(client_id、client_secret)
* 方式一:client_id、client_secret放在请求路径中
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
*
* @return
*/
@SneakyThrows
public static String getOAuth2ClientId() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 从请求路径中获取
String clientId = request.getParameter(AuthConstants.CLIENT_ID_KEY);
if (StrUtil.isNotBlank(clientId)) {
return clientId;
}
// 从请求头获取
String basic = request.getHeader(AuthConstants.AUTHORIZATION_KEY);
if (StrUtil.isNotBlank(basic) && basic.startsWith(AuthConstants.BASIC_PREFIX)) {
basic = basic.replace(AuthConstants.BASIC_PREFIX, Strings.EMPTY);
String basicPlainText = new String(Base64.getDecoder().decode(basic.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
clientId = basicPlainText.split(":")[0]; //client:secret
}
return clientId;
}
/**
* 解析JWT获取获取认证方式
*
* @return
*/
@SneakyThrows
public static String getAuthenticationMethod() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String refreshToken = request.getParameter(AuthConstants.REFRESH_TOKEN);
String payload = StrUtil.toString(JWSObject.parse(refreshToken).getPayload());
JSONObject jsonObject = JSONUtil.parseObj(payload);
String authenticationMethod = jsonObject.getStr(AuthConstants.AUTHENTICATION_METHOD);
if (StrUtil.isBlank(authenticationMethod)) {
authenticationMethod = AuthenticationMethodEnum.USERNAME.getValue();
}
return authenticationMethod;
}
}
......@@ -102,49 +102,21 @@
<artifactId>common-redis</artifactId>
</dependency>
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.2.2</version>
<!--将插件绑定在某个阶段执行-->
<executions>
<execution>
<id>build-image</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<imageName>${project.artifactId}</imageName>
<!--指定标签-->
<imageTags>
<imageTag>latest</imageTag>
</imageTags>
<!-- 指定 Dockerfile 路径-->
<dockerDirectory>${project.basedir}/src/main/docker</dockerDirectory>
<forceTags>true</forceTags>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</build>
......
package com.youlai.gateway.kaptcha.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/10/4
*/
@Configuration
public class CaptchaConfig {
@Bean(name = "captchaProducerMath")
public DefaultKaptcha getKaptchaBeanMath() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty("kaptcha.border", "yes");
// 边框颜色 默认为Color.BLACK
properties.setProperty("kaptcha.border.color", "105,179,90");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty("kaptcha.textproducer.font.color", "blue");
// 验证码图片宽度 默认为200
properties.setProperty("kaptcha.image.width", "160");
// 验证码图片高度 默认为50
properties.setProperty("kaptcha.image.height", "60");
// 验证码文本字符大小 默认为40
properties.setProperty("kaptcha.textproducer.font.size", "35");
// KAPTCHA_SESSION_KEY
properties.setProperty("kaptcha.session.key", "kaptchaCodeMath");
// 验证码文本生成器
properties.setProperty("kaptcha.textproducer.impl", "com.youlai.gateway.kaptcha.handler.KaptchaTextCreator");
// 验证码文本字符间距 默认为2
properties.setProperty("kaptcha.textproducer.char.space", "3");
// 验证码文本字符长度 默认为5
properties.setProperty("kaptcha.textproducer.char.length", "6");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1,
// fontSize)
properties.setProperty("kaptcha.textproducer.font.names", "Arial,Courier");
// 验证码噪点颜色 默认为Color.BLACK
properties.setProperty("kaptcha.noise.color", "white");
// 干扰实现类
properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple
// 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy
// 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
package com.youlai.gateway.kaptcha.handler;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.util.IdUtil;
import com.google.code.kaptcha.Producer;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/10/4
*/
@Component
@RequiredArgsConstructor
public class CaptchaImageHandler implements HandlerFunction<ServerResponse> {
//随机数code_key
public static final String DEFAULT_CODE_KEY = "random_code_";
private final Producer producer;
private final RedisTemplate redisTemplate;
@Override
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
// 生成验证码
String capText = producer.createText();
String capStr = capText.substring(0, capText.lastIndexOf("@"));
String code = capText.substring(capText.lastIndexOf("@") + 1);
BufferedImage image = producer.createImage(capStr);
// 保存验证码信息
String randomStr = IdUtil.simpleUUID();
redisTemplate.opsForValue().set(DEFAULT_CODE_KEY + randomStr, code, 60, TimeUnit.SECONDS);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try {
ImageIO.write(image, "jpg", os);
} catch (IOException e) {
return Mono.error(e);
}
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.IMAGE_JPEG)
.header("randomstr", randomStr)
.body(BodyInserters.fromResource(new ByteArrayResource(os.toByteArray())));
}
}
package com.youlai.gateway.kaptcha.handler;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
* 验证码文本生成器
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/10/4
*/
public class KaptchaTextCreator extends DefaultTextCreator {
private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
private SecureRandom rand = SecureRandom.getInstanceStrong();
public KaptchaTextCreator() throws NoSuchAlgorithmException {
}
@Override
public String getText() {
Integer result = 0;
int x = this.rand.nextInt(10);
int y = this.rand.nextInt(10);
StringBuilder suChinese = new StringBuilder();
int randomoperands = (int) Math.round(rand.nextDouble() * 2);
if (randomoperands == 0) {
result = x * y;
suChinese.append(CNUMBERS[x]);
suChinese.append("*");
suChinese.append(CNUMBERS[y]);
} else if (randomoperands == 1) {
if (!(x == 0) && y % x == 0) {
result = y / x;
suChinese.append(CNUMBERS[y]);
suChinese.append("/");
suChinese.append(CNUMBERS[x]);
} else {
result = x + y;
suChinese.append(CNUMBERS[x]);
suChinese.append("+");
suChinese.append(CNUMBERS[y]);
}
} else if (randomoperands == 2) {
if (x >= y) {
result = x - y;
suChinese.append(CNUMBERS[x]);
suChinese.append("-");
suChinese.append(CNUMBERS[y]);
} else {
result = y - x;
suChinese.append(CNUMBERS[y]);
suChinese.append("-");
suChinese.append(CNUMBERS[x]);
}
} else {
result = x + y;
suChinese.append(CNUMBERS[x]);
suChinese.append("+");
suChinese.append(CNUMBERS[y]);
}
suChinese.append("=?@" + result);
return suChinese.toString();
}
}
package com.youlai.gateway.router;
import com.youlai.gateway.kaptcha.handler.CaptchaImageHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
/**
* 图片验证码路由
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/10/4
*/
@Configuration
public class CaptchaImageRouter {
@Bean
public RouterFunction<ServerResponse> routeFunction(CaptchaImageHandler captchaImageHandler) {
return RouterFunctions
.route(RequestPredicates.GET("/code")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), captchaImageHandler::handle);
}
}
......@@ -68,8 +68,7 @@ public class SecurityGlobalFilter implements GlobalFilter, Ordered {
// 解析JWT获取jti,以jti为key判断redis的黑名单列表是否存在,存在则拦截访问
token = StrUtil.replaceIgnoreCase(token,AuthConstants.JWT_PREFIX, Strings.EMPTY);
JWSObject jwsObject = JWSObject.parse(token);
String payload = jwsObject.getPayload().toString();
String payload = StrUtil.toString(JWSObject.parse(token).getPayload());
JSONObject jsonObject = JSONUtil.parseObj(payload);
String jti = jsonObject.getStr(AuthConstants.JWT_JTI);
Boolean isBlack = redisTemplate.hasKey(AuthConstants.TOKEN_BLACKLIST_PREFIX + jti);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册