微信小程序前期开发准备,可以参考这篇文章微信小程序前期准备 1、学习过Spring Secrity oauth2.0的都知道,他有四种登录模式可以选择 authorization code(授权码模式) implicit(简化模式) resource owner password credentials(密码模式) client credentials(客户端模式) 前三种模式都需要用户的密码才能认证成功,客户端模式虽然不需要密码,但是也不会跟用户绑定。所以也是不符合的。我们去微信拿到用户的认证之后,需要自己的系统认证通过,然后返回token给前端。如果系统采用oauth2.0来做认证,这时候我们是没办法拿到用户的明文密码的。并且一般密码都是用BCryptPasswordEncoder加密处理,是不可逆的。这个时候,我们虽然通过了微信的认证,但是如何通过自身系统的认证就是个问题了。那么这时候就需要自定义oauth2.0的授权模式了,通过微信返回的用户唯一标识来完成认证。 这里的方法,适用用于任何的第三方登录: 首先,我们需要一个拦截器: 登录接口如下
9999端口是gateway网关端口。auth是网关路由
http://192.168.2.171:9999/auth/custom/token/social?grant_type=custom&custom=WXCUSTOM@{这后面是微信获取的code}
头信息:Basic d3hNaW5pQXBwQ3VzdG9tOnd4TWluaUFwcEN1c3RvbQ==(这后面是BASE64加密信息)
登录URL拦截器
注意这里是 filter 来接管这个请求,而不是到 Controller)
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SocialAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String SPRING_SECURITY_FORM_CUSTOM_KEY = "custom";
@Getter
@Setter
private String customWxLogin = SPRING_SECURITY_FORM_CUSTOM_KEY;
@Getter
@Setter
private AuthenticationEventPublisher eventPublisher;
@Getter
@Setter
private boolean postOnly = true;
@Getter
@Setter
private AuthenticationEntryPoint authenticationEntryPoint;
public SocialAuthenticationFilter() {
super(new AntPathRequestMatcher(SecurityConstants.CUSTOM_TOKE_URL, "POST"));
}
@Override
@SneakyThrows
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String mobile = obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
SocialAuthenticationToken socialAuthenticationToken = new SocialAuthenticationToken(mobile);
setDetails(request, socialAuthenticationToken);
Authentication authResult = null;
try {
authResult = this.getAuthenticationManager().authenticate(socialAuthenticationToken);
logger.debug("Authentication success: " + authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
} catch (Exception failed) {
SecurityContextHolder.clearContext();
logger.debug("Authentication request failed: " + failed);
eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
try {
authenticationEntryPoint.commence(request, response,
new UsernameNotFoundException(failed.getMessage(), failed));
} catch (Exception e) {
logger.error("authenticationEntryPoint handle error:{}", failed);
}
}
return authResult;
}
private String obtainMobile(HttpServletRequest request) {
return request.getParameter(customWxLogin);
}
private void setDetails(HttpServletRequest request, SocialAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
注:里面有一些常量,我这里也给出方法代码
public interface SecurityConstants {
boolean INNER_CHECK = true;
String REFRESH_TOKEN = "refresh_token";
int CODE_TIME = 60;
String CODE_SIZE = "4";
String ROLE = "ROLE_";
String PIGX_PREFIX = "pigx_";
String TOKEN_PREFIX = "token:";
String OAUTH_PREFIX = "oauth:";
String OAUTH_CODE_PREFIX = "oauth:code:";
String PIGX_LICENSE = "made by HuaBing";
String FROM_IN = "Y";
String FROM = "from";
String OAUTH_TOKEN_URL = "/oauth/token";
String SMS_TOKEN_URL = "/mobile/token/sms";
String SOCIAL_TOKEN_URL = "/mobile/token/social";
String MOBILE_TOKEN_URL = "/mobile/token/*";
String CUSTOM_TOKEN = "/custom/token/social";
String CUSTOM_TOKE_URL = "/custom/token/*";
String WX_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/oauth2/access_token"
+ "?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
String MINI_APP_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/jscode2session"
+ "?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";
String CUSTOM_WX_MINI_LOGIN_URL = "https://api.weixin.qq.com/sns/jscode2session";
String CUSTOM_WX_MINI_LOGIN_TYPE = "authorization_code";
String CUSTOM_APPID = "wx94e1329ed5419b55";
String CUSTOM_SECRET="6d09e94f564d263340da3a3ede4ca6ab";
String GITEE_AUTHORIZATION_CODE_URL = "https://gitee.com/oauth/token?grant_type="
+ "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s";
String OSC_AUTHORIZATION_CODE_URL = "https://www.oschina.net/action/openapi/token";
String GITEE_USER_INFO_URL = "https://gitee.com/api/v5/user?access_token=%s";
String OSC_USER_INFO_URL = "https://www.oschina.net/action/openapi/user?access_token=%s&dataType=json";
String BCRYPT = "{bcrypt}";
String CLIENT_FIELDS = "client_id, CONCAT('{noop}',client_secret) as client_secret, resource_ids, scope, "
+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+ "refresh_token_validity, additional_information, autoapprove";
String BASE_FIND_STATEMENT = "select " + CLIENT_FIELDS + " from sys_oauth_client_details";
String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ? and del_flag = 0 and tenant_id = %s";
String RESOURCE_SERVER_CONFIGURER = "resourceServerConfigurerAdapter";
String CLIENT_CREDENTIALS = "client_credentials";
String CLIENT_ID = "client_id";
String DETAILS_USER_ID = "id";
String DETAILS_USERNAME = "username";
String DETAILS_USER = "user_info";
String DETAILS_PHONE = "phone";
String DETAILS_AVATAR = "avatar";
String DETAILS_DEPT_ID = "deptId";
String DETAILS_TENANT_ID = "tenantId";
String DETAILS_LICENSE = "license";
String ACTIVE = "active";
String AES = "aes";
}
获取客户登录的信息
(注:我这里分后台用户登录,前台客户登录,不同用户查询两张不同的表)
package com.pig4cloud.pigx.common.security.custom;
import com.pig4cloud.pigx.common.security.component.PigxPreAuthenticationChecks;
import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService;
import com.pig4cloud.pigx.common.security.util.PigxSecurityMessageSourceUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
@Slf4j
public class SocialAuthenticationProvider implements AuthenticationProvider {
private MessageSourceAccessor messages = PigxSecurityMessageSourceUtil.getAccessor();
private UserDetailsChecker detailsChecker = new PigxPreAuthenticationChecks();
@Getter
@Setter
private PigxUserDetailsService userDetailsService;
@Override
@SneakyThrows
public Authentication authenticate(Authentication authentication) {
SocialAuthenticationToken socialAuthenticationToken = (SocialAuthenticationToken) authentication;
String principal = socialAuthenticationToken.getPrincipal().toString();
UserDetails userDetails = userDetailsService.customLoadBySocial(principal);
if (userDetails == null) {
log.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages
.getMessage("AbstractUserDetailsAuthenticationProvider.noopBindAccount", "Noop Bind Account"));
}
detailsChecker.check(userDetails);
SocialAuthenticationToken authenticationToken = new SocialAuthenticationToken(userDetails,
userDetails.getAuthorities());
authenticationToken.setDetails(socialAuthenticationToken.getDetails());
return authenticationToken;
}
@Override
public boolean supports(Class<?> authentication) {
return SocialAuthenticationToken.class.isAssignableFrom(authentication);
}
}
获取用户信息的方法
public interface PigxUserDetailsService extends UserDetailsService {
UserDetails customLoadBySocial(String code) throws UsernameNotFoundException;
}
==============================================分割================================================
@Slf4j
@Primary
@RequiredArgsConstructor
public class PigxUserDetailsServiceImpl implements PigxUserDetailsService {
private final RemoteUserService remoteUserService;
private final RemoteCustomService remoteCustomService;
private final CacheManager cacheManager;
private final TokenStore tokenStore;
@Override
@SneakyThrows
public UserDetails customLoadBySocial(String code) {
FoodMacCustom customSocialInfo = remoteCustomService.getCustomSocialInfo(code, SecurityConstants.FROM_IN).getData();
UserDetails customDetails = getCustomDetails(customSocialInfo);
return customDetails;
}
private UserDetails getCustomDetails(FoodMacCustom foodMacCustom) {
if (foodMacCustom == null) {
throw new UsernameNotFoundException("客户未注册");
}
boolean is_lock = false;
if (CommonConstants.CUSTOM_LOCK.equals(foodMacCustom.getStatus())) {
is_lock = true;
}
if (is_lock == false) {
throw new PigxAuth2Exception("账号已被锁定");
}
log.debug("用户信息::{}", foodMacCustom.getUsername());
PigxCustomUser pigxCustomUser = new PigxCustomUser(foodMacCustom.getId(), 1, foodMacCustom.getMobile(), foodMacCustom.getAvatar(), foodMacCustom.getTenantId(),
foodMacCustom.getUsername(), "{noop}" + foodMacCustom.getPassword(), true, true,
true, is_lock, AuthorityUtils.NO_AUTHORITIES);
return pigxCustomUser;
}
}
===================================================分割==============================================================
public interface SysSocialDetailsService extends IService<SysSocialDetails> {
FoodMacCustom getCustomSocialInfo(String code);
}
===================================================分割==============================================================
@Slf4j
@AllArgsConstructor
@Service("sysSocialDetailsService")
public class SysSocialDetailsServiceImpl extends ServiceImpl<SysSocialDetailsMapper, SysSocialDetails>
implements SysSocialDetailsService {
private final Map<String, CustomLoginHandle> customLoginHandlerMap;
private final CacheManager cacheManager;
private final SysUserMapper sysUserMapper;
@Override
public FoodMacCustom getCustomSocialInfo(String code) {
String[] inStrs = code.split(StringPool.AT);
String type = inStrs[0];
String loginStr = inStrs[1];
FoodMacCustom foodMacCustom = customLoginHandlerMap.get(type).handle(loginStr);
return foodMacCustom;
}
}
===================================================分割==============================================================
public interface CustomLoginHandle {
Boolean check(String loginStr);
String identify(String loginStr);
FoodMacCustom customInfo(String identify);
FoodMacCustom handle(String loginStr);
}
===================================================分割==============================================================
public abstract class AbstractCustomLoginHandler implements CustomLoginHandle {
@Override
public Boolean check(String loginStr) {
return true;
}
@Override
public FoodMacCustom handle(String loginStr) {
if (!check(loginStr)) {
return null;
}
String identify = identify(loginStr);
FoodMacCustom foodMacCustom = customInfo(identify);
return foodMacCustom;
}
}
===================================================分割==============================================================
@Slf4j
@Component("WXCUSTOM")
@AllArgsConstructor
public class CustomWeChatLoginHandler extends AbstractCustomLoginHandler {
private final IFoodMacCustomService foodMacCustomService;
private final SysSocialDetailsMapper sysSocialDetailsMapper;
private final StringRedisTemplate stringRedisTemplate;
private static final String REDIS_KEY = "WX:CUSTOM:LOGIN:";
private final Sequence sequence;
private static final long EXPIRE_TIME = 7200000;
@Override
public String identify(String code) {
String sessionKeyOpenId = (String) stringRedisTemplate.opsForValue().get(REDIS_KEY + code);
if (StrUtil.isEmpty(sessionKeyOpenId)) {
SysSocialDetails condition = new SysSocialDetails();
condition.setType(LoginTypeEnum.WECHAT.getType());
SysSocialDetails socialDetails = sysSocialDetailsMapper.selectOne(new QueryWrapper<>(condition));
log.debug("客户--->{}", code);
String result = sendGet(SecurityConstants.CUSTOM_WX_MINI_LOGIN_URL,
"appid=" + socialDetails.getAppId() + "&" +
"secret=" + socialDetails.getAppSecret() + "&" +
"js_code=" + code + "&" +
"grant_type=" + SecurityConstants.CUSTOM_WX_MINI_LOGIN_TYPE);
log.info("微信响应报文:{}", result);
JSONObject jsonObject = JSON.parseObject(result);
String openId = jsonObject.getString("openid");
String sessionKey = jsonObject.getString("session_key");
log.info("微信openId::{}", openId);
log.info("微信sessionKey::{}", sessionKey);
if (openId != null && !"".equals(openId)) {
log.info("+++++++++++++微信key+++++++++++{}", sessionKey);
String redisKey = REDIS_KEY + code;
StringBuilder stringBuilder = new StringBuilder();
String openIdSessionKey = stringBuilder.append(sessionKey).append("@").append(openId).toString();
stringRedisTemplate.opsForValue().set(redisKey,openIdSessionKey,EXPIRE_TIME, TimeUnit.SECONDS);
}
return openId;
} else if (StrUtil.isNotEmpty(sessionKeyOpenId)) {
String[] split = sessionKeyOpenId.split("@");
String session = split[0];
String openId = split[1];
return openId;
}
return null;
}
@Override
public FoodMacCustom customInfo(String openId) {
FoodMacCustom custom = foodMacCustomService.getOne(Wrappers.<FoodMacCustom>query().lambda()
.eq(FoodMacCustom::getWechatopenid, openId));
if (custom == null) {
log.info("微信未绑定:{}", openId);
return null;
}
return custom;
}
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
Map<String, List<String>> map = connection.getHeaderFields();
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
}
远程调用的controller和fegin调用方法截图
生成token令牌:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.SneakyThrows;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
public class SocialAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
public SocialAuthenticationToken(String mobile) {
super(null);
this.principal = mobile;
setAuthenticated(false);
}
@JsonCreator
public SocialAuthenticationToken(@JsonProperty("principal") Object principal,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public Object getCredentials() {
return null;
}
@Override
@SneakyThrows
public void setAuthenticated(boolean isAuthenticated) {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
第三方资源配置入口:
```java
package com.pig4cloud.pigx.common.security.custom;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pig4cloud.pigx.common.security.component.PigxCommenceAuthExceptionEntryPoint;
import com.pig4cloud.pigx.common.security.handler.CustomLoginSuccessHandler;
import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Getter
@Setter
public class SocialSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private AuthenticationEventPublisher defaultAuthenticationEventPublisher;
@Autowired
private CustomLoginSuccessHandler socialLoginSuccessHandler;
@Autowired
private PigxUserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity http) {
SocialAuthenticationFilter socialAuthenticationFilter = new SocialAuthenticationFilter();
socialAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
socialAuthenticationFilter.setAuthenticationSuccessHandler(socialLoginSuccessHandler);
socialAuthenticationFilter.setEventPublisher(defaultAuthenticationEventPublisher);
socialAuthenticationFilter.setAuthenticationEntryPoint(new PigxCommenceAuthExceptionEntryPoint(objectMapper));
SocialAuthenticationProvider socialAuthenticationProvider = new SocialAuthenticationProvider();
socialAuthenticationProvider.setUserDetailsService(userDetailsService);
http.authenticationProvider(socialAuthenticationProvider).addFilterAfter(socialAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class);
}
}
|