1、前置准备参考 https://blog.csdn.net/qq_34190023/article/details/81133619
2、微信登录实现流程图
在这里插入图片描述
3、oauth自定义授权模式
上图大概描述了微信登录的一个流程。如果你是用oauth2.0,就会存在一个问题。oauth2.0自身支持四种授权模式,分别是:

  • authorization code(授权码模式)
  • implicit(简化模式)
  • resource owner password credentials(密码模式)
  • client credentials(客户端模式)

前三种模式都需要用户的密码才能认证成功,客户端模式虽然不需要密码,但是也不会跟用户绑定。所以也是不符合的。我们去微信拿到用户的认证之后,需要自己的系统认证通过,然后返回token给前端。如果系统采用oauth2.0来做认证,这时候我们是没办法拿到用户的明文密码的。并且一般密码都是用BCryptPasswordEncoder加密处理,是不可逆的。这个时候,我们虽然通过了微信的认证,但是如何通过自身系统的认证就是个问题了。那么这时候就需要自定义oauth2.0的授权模式了,通过微信返回的用户唯一标识来完成认证。
注:自定义授权模式适用于任何第三方登录
附上核心代码:

/**
 *
 * 第三方登录验证filter
 */
public class SocialAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

   private static final String SPRING_SECURITY_FORM_MOBILE_KEY = "social";

   @Getter
   @Setter
   private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;

   @Getter
   @Setter
   private boolean postOnly = true;

   @Getter
   @Setter
   private AuthenticationEventPublisher eventPublisher;

   @Getter
   @Setter
   private AuthenticationEntryPoint authenticationEntryPoint;

   public SocialAuthenticationFilter() {
      super(new AntPathRequestMatcher("/social/token", "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(mobileParameter);
   }

   private void setDetails(HttpServletRequest request, SocialAuthenticationToken authRequest) {
      authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
   }

}
/**
 *  社交登录
 */
@Slf4j
public class SocialAuthenticationProvider implements AuthenticationProvider {

   private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

   private UserDetailsChecker detailsChecker = new PreAuthenticationChecks();

   @Getter
   @Setter
   private UserDetailsService socialUserDetailService;

   @Override
   @SneakyThrows
   public Authentication authenticate(Authentication authentication) {
      SocialAuthenticationToken socialAuthenticationToken = (SocialAuthenticationToken) authentication;

      String principal = socialAuthenticationToken.getPrincipal().toString();
      UserDetails userDetails = socialUserDetailService.loadUserByUsername(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 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);
   }

   public SocialAuthenticationToken(Object principal, 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();
   }

}
/**
 *
 * 第三方登录配置入口
 */
@Getter
@Setter
public class SocialSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

   @Autowired
   private ObjectMapper objectMapper;

   @Autowired
   private AuthenticationEventPublisher defaultAuthenticationEventPublisher;

   @Autowired
   private AuthenticationSuccessHandler socialLoginSuccessHandler;

   @Autowired
   private UserDetailsService socialUserDetailService;

   @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 ResourceAuthExceptionEntryPoint(objectMapper));

      SocialAuthenticationProvider socialAuthenticationProvider = new SocialAuthenticationProvider();
      socialAuthenticationProvider.setSocialUserDetailService(socialUserDetailService);
      http.authenticationProvider(socialAuthenticationProvider).addFilterAfter(socialAuthenticationFilter,
            UsernamePasswordAuthenticationFilter.class);
   }

}
@Slf4j
public class PreAuthenticationChecks implements UserDetailsChecker {

	private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	@Override
	public void check(UserDetails user) {
		if (!user.isAccountNonLocked()) {
			log.debug("User account is locked");

			throw new LockedException(
					messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
		}

		if (!user.isEnabled()) {
			log.debug("User account is disabled");

			throw new DisabledException(
					messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
		}

		if (!user.isAccountNonExpired()) {
			log.debug("User account is expired");

			throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",
					"User account has expired"));
		}
	}

}

附上测试请求示例:code对应微信返回用户唯一标识。即可完成本系统认证,拿到token。

Map<String, Object> params = new HashMap<>();
params.put("grant_type","social");
params.put("social",code);
params.put("client_id","test");
params.put("client_secret","test");
String token = OkHttpUtils.doFromPost("http://localhost:端口号/auth/social/token", params);
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐