feat:springsecurity偶通话

This commit is contained in:
有来技术 2021-10-05 23:58:20 +08:00
parent aa031f46bf
commit b41d67af58
14 changed files with 329 additions and 37 deletions

View File

@ -10,8 +10,9 @@ 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.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.wechat.WechatTokenGranter;
import com.youlai.auth.security.extension.PreAuthenticatedUserDetailsService;
import com.youlai.auth.security.extension.username.CaptchaTokenGranter;
import com.youlai.common.result.Result;
import com.youlai.common.result.ResultCode;
import lombok.RequiredArgsConstructor;
@ -19,6 +20,7 @@ import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
@ -54,6 +56,7 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
private final ClientDetailsServiceImpl clientDetailsService;
private final SysUserDetailsServiceImpl sysUserDetailsService;
private final MemberUserDetailsServiceImpl memberUserDetailsService;
private final StringRedisTemplate stringRedisTemplate;
/**
* OAuth2客户端
@ -78,10 +81,13 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
// 添加自定义授权模式
List<TokenGranter> granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));
granterList.add(new WechatTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(),
authenticationManager));
CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);
granterList.add(new WechatTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory(), authenticationManager));
granterList.add(new CaptchaTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory(), authenticationManager, stringRedisTemplate
));
CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(jwtAccessTokenConverter())
@ -112,21 +118,16 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
tokenServices.setTokenEnhancer(tokenEnhancerChain);
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new PreAuthenticatedUserDetailsService<>(refreshTokenUserDetailsServiceMap()));
Map<String, UserDetailsService> clientUserDetailsServiceMap = new HashMap<>();
clientUserDetailsServiceMap.put(AuthConstants.ADMIN_CLIENT_ID, sysUserDetailsService);
clientUserDetailsServiceMap.put(AuthConstants.APP_CLIENT_ID, memberUserDetailsService);
provider.setPreAuthenticatedUserDetailsService(new PreAuthenticatedUserDetailsService<>(clientUserDetailsServiceMap));
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签名
*/

View File

@ -1,7 +1,7 @@
package com.youlai.auth.security.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import com.youlai.auth.security.extension.memeber.wechat.WechatAuthenticationProvider;
import com.youlai.auth.security.extension.wechat.WechatAuthenticationProvider;
import com.youlai.mall.ums.api.MemberFeignClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

View File

@ -0,0 +1,76 @@
package com.youlai.auth.security.extension.mobile;
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.auth.security.extension.wechat.WechatAuthenticationToken;
import com.youlai.auth.security.extension.wechat.WechatUserInfo;
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 me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import java.util.HashSet;
/**
* 手机短信验证码提供者
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/9/25
*/
@Data
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
private WxMaService wxMaService;
private MemberFeignClient memberFeignClient;
/**
* 微信认证
*
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WechatAuthenticationToken authenticationToken = (WechatAuthenticationToken) authentication;
String code = (String) authenticationToken.getPrincipal();
WechatUserInfo wechatUserInfo = authenticationToken.getWechatUserInfo();
WxMaJscode2SessionResult sessionInfo = null;
try {
sessionInfo = wxMaService.getUserService().getSessionInfo(code);
} catch (WxErrorException e) {
e.printStackTrace();
}
String openid = sessionInfo.getOpenid();
Result<MemberAuthDTO> memberAuthResult = memberFeignClient.loadUserByOpenId(openid);
// 微信用户不存在注册成为新会员
if (memberAuthResult != null && ResultCode.USER_NOT_EXIST.getCode().equals(memberAuthResult.getCode())) {
UmsMember member = new UmsMember();
BeanUtil.copyProperties(wechatUserInfo, member);
member.setOpenid(openid);
memberFeignClient.add(member);
}
UserDetails userDetails = ((MemberUserDetailsServiceImpl) userDetailsService).loadUserByOpenId(openid);
WechatAuthenticationToken result = new WechatAuthenticationToken(userDetails, new HashSet<>());
result.setDetails(authentication.getDetails());
return result;
}
@Override
public boolean supports(Class<?> authentication) {
return WechatAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@ -0,0 +1,52 @@
package com.youlai.auth.security.extension.mobile;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
import java.util.Collection;
/**
* 手机验证码登录
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/10/5
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 550L;
private final Object principal;
private Object credentials;
public SmsCodeAuthenticationToken(Object principal, Object credentials) {
super((Collection) null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public SmsCodeAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}

View File

@ -0,0 +1,67 @@
package com.youlai.auth.security.extension.mobile;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.youlai.common.constant.AuthConstants;
import com.youlai.common.web.exception.BizException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 手机验证码
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/9/25
*/
public class SmsCodeTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "sms_code";
private final AuthenticationManager authenticationManager;
private StringRedisTemplate redisTemplate;
public SmsCodeTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager,
StringRedisTemplate redisTemplate
) {
super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
this.redisTemplate = redisTemplate;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
String mobile = parameters.get("mobile"); // 手机号
String code = parameters.get("code"); // 短信验证码
parameters.remove("code");
Authentication userAuth = new SmsCodeAuthenticationToken(mobile, code);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = this.authenticationManager.authenticate(userAuth);
} catch (AccountStatusException var8) {
throw new InvalidGrantException(var8.getMessage());
} catch (BadCredentialsException var9) {
throw new InvalidGrantException(var9.getMessage());
}
if (userAuth != null && userAuth.isAuthenticated()) {
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else {
throw new InvalidGrantException("Could not authenticate user: " + mobile);
}
}
}

View File

@ -0,0 +1,81 @@
package com.youlai.auth.security.extension.username;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.youlai.common.constant.AuthConstants;
import com.youlai.common.web.exception.BizException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 验证码授权模式
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/9/25
*/
public class CaptchaTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "captcha";
private final AuthenticationManager authenticationManager;
private StringRedisTemplate redisTemplate;
public CaptchaTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager,
StringRedisTemplate redisTemplate
) {
super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
this.redisTemplate = redisTemplate;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
String validateCode = parameters.get("validateCode");
String uuid = parameters.get("uuid");
Assert.isTrue(StrUtil.isNotBlank(validateCode), "验证码不能为空");
String validateCodeKey = AuthConstants.VALIDATE_CODE_PREFIX + uuid;
String correctValidateCode = redisTemplate.opsForValue().get(validateCodeKey);
if (!validateCode.equals(correctValidateCode)) {
throw new BizException("验证码不正确");
} else {
redisTemplate.delete(validateCodeKey);
}
String username = parameters.get("username");
String password = parameters.get("password");
parameters.remove("password");
parameters.remove("validateCode");
parameters.remove("uuid");
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = this.authenticationManager.authenticate(userAuth);
} catch (AccountStatusException var8) {
throw new InvalidGrantException(var8.getMessage());
} catch (BadCredentialsException var9) {
throw new InvalidGrantException(var9.getMessage());
}
if (userAuth != null && userAuth.isAuthenticated()) {
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
}
}

View File

@ -1,4 +1,4 @@
package com.youlai.auth.security.extension.memeber.wechat;
package com.youlai.auth.security.extension.wechat;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
@ -20,6 +20,8 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import java.util.HashSet;
/**
* 微信认证提供者
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/9/25
*/
@ -31,7 +33,7 @@ public class WechatAuthenticationProvider implements AuthenticationProvider {
private MemberFeignClient memberFeignClient;
/**
* 证验
* 微信认证
*
* @param authentication
* @return
@ -58,8 +60,7 @@ public class WechatAuthenticationProvider implements AuthenticationProvider {
member.setOpenid(openid);
memberFeignClient.add(member);
}
UserDetails userDetails = ((MemberUserDetailsServiceImpl)userDetailsService).loadUserByOpenId(openid);
UserDetails userDetails = ((MemberUserDetailsServiceImpl) userDetailsService).loadUserByOpenId(openid);
WechatAuthenticationToken result = new WechatAuthenticationToken(userDetails, new HashSet<>());
result.setDetails(authentication.getDetails());
return result;

View File

@ -1,4 +1,4 @@
package com.youlai.auth.security.extension.memeber.wechat;
package com.youlai.auth.security.extension.wechat;
import lombok.Getter;
import org.springframework.security.authentication.AbstractAuthenticationToken;

View File

@ -1,4 +1,4 @@
package com.youlai.auth.security.extension.memeber.wechat;
package com.youlai.auth.security.extension.wechat;
import cn.hutool.json.JSONUtil;
import org.springframework.security.authentication.*;

View File

@ -1,4 +1,4 @@
package com.youlai.auth.security.extension.memeber.wechat;
package com.youlai.auth.security.extension.wechat;
import lombok.Data;

View File

@ -72,5 +72,15 @@ public interface AuthConstants {
*/
String AUTHENTICATION_METHOD = "authenticationMethod";
/**
* 验证码key前缀
*/
String VALIDATE_CODE_PREFIX = "VALIDATE_CODE:";
/**
* 短信验证码key前缀
*/
String SMS_CODE_PREFIX = "SMS_CODE:";
}

View File

@ -8,6 +8,8 @@ import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* Kaptcha 验证码配置
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/10/4
*/

View File

@ -1,13 +1,15 @@
package com.youlai.gateway.kaptcha.handler;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.util.IdUtil;
import com.google.code.kaptcha.Producer;
import com.youlai.common.constant.AuthConstants;
import com.youlai.common.result.Result;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
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;
@ -18,8 +20,10 @@ import reactor.core.publisher.Mono;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
/**
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/10/4
@ -28,12 +32,8 @@ import java.util.concurrent.TimeUnit;
@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;
private final StringRedisTemplate redisTemplate;
@Override
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
@ -43,8 +43,8 @@ public class CaptchaImageHandler implements HandlerFunction<ServerResponse> {
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);
String uuid = IdUtil.simpleUUID();
redisTemplate.opsForValue().set(AuthConstants.VALIDATE_CODE_PREFIX + uuid, code, 60, TimeUnit.SECONDS);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try {
@ -52,12 +52,14 @@ public class CaptchaImageHandler implements HandlerFunction<ServerResponse> {
} 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())));
java.util.Map resultMap = new HashMap<String, String>();
resultMap.put("uuid", uuid);
resultMap.put("img", Base64.encode(os.toByteArray()));
return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(Result.success(resultMap)));
}
}

View File

@ -21,7 +21,7 @@ public class CaptchaImageRouter {
@Bean
public RouterFunction<ServerResponse> routeFunction(CaptchaImageHandler captchaImageHandler) {
return RouterFunctions
.route(RequestPredicates.GET("/code")
.route(RequestPredicates.GET("/validate-code")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), captchaImageHandler::handle);
}