APP邮箱登录,Facebook,Google以及appId如何登录手机App

2025-10-03 04:19:41

相信大家都了解过Oauth2.0以及SPring Security在安全认证和登录上的作用,很多会使用oauth2,0的grant_type来进行第三方授权登录。今天为大家讲解新的手机app第三方登录。

1.WebSecurityConfigurerAdapter的核心作用是简化 Spring Security 的配置流程,通过继承该类并覆写特定方法,开发者可以轻松定制应用的安全规则,无需从零实现复杂的安全配置接口。具体来说,它的主要功能包括:

配置 HTTP 请求的访问控制

例如:指定哪些 URL 需要登录后访问、哪些 URL 可以匿名访问、哪些 URL 需要特定角色权限等。

配置用户认证方式

例如:基于内存的用户存储、基于数据库的用户查询(结合UserDetailsService)、OAuth2 第三方登录等。

配置安全相关的细节

例如:CSRF 防护开关、会话管理策略(如会话创建、过期处理)、自定义登录页 / 登出页、异常处理(如 403 权限不足页面)等。

常用方法

继承WebSecurityConfigurerAdapter后,通常需要覆写以下两个核心方法:

configure(HttpSecurity http)

用于配置 HTTP 请求的安全规则,是最常用的方法。例如:

java

运行

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.authorizeRequests() // 配置请求授权

.antMatchers("/public/").permitAll() // 公开路径允许匿名访问

.antMatchers("/admin/").hasRole("ADMIN") // 管理员路径需要ADMIN角色

.anyRequest().authenticated() // 其他所有请求需要认证

.and()

.formLogin() // 配置表单登录

.loginPage("/login") // 自定义登录页

.permitAll() // 登录页允许匿名访问

.and()

.logout() // 配置登出

.permitAll();

}

configure(AuthenticationManagerBuilder auth)

用于配置用户认证的数据源(即 “谁可以登录”)。例如:

java

运行

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

// 基于内存的用户配置(仅用于测试)

auth.inMemoryAuthentication()

.withUser("user").password("{noop}123456").roles("USER")

.and()

.withUser("admin").password("{noop}123456").roles("ADMIN");

}

注:{noop}表示不加密(仅测试用),实际开发需使用密码编码器(如BCryptPasswordEncoder)。

注意事项

已过时:在 Spring Security 5.7.0 及以上版本中,WebSecurityConfigurerAdapter已被标记为过时(deprecated)。官方推荐使用组件式配置(通过@Bean定义SecurityFilterChain、UserDetailsService等)替代继承该类的方式。

例如,替代configure(HttpSecurity http)的新方式:

java

运行

@Bean

public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

http

.authorizeRequests()

.antMatchers("/public/**").permitAll()

.anyRequest().authenticated()

.and()

.formLogin();

return http.build();

}

核心思想不变:无论是否使用WebSecurityConfigurerAdapter,Spring Security 的配置核心都是定义 “哪些资源需要保护”“谁能访问这些资源”“如何验证身份”,只是实现方式从 “继承类覆写方法” 变为 “直接定义 Bean”。

2.了解完springsecurity开始进入手机第三方登录流程

@Override

@SneakyThrows

protected void configure(HttpSecurity http) {

http

.formLogin()

.loginPage("/token/login")

.loginProcessingUrl("/token/form")

.failureHandler(authenticationFailureHandler())

.and()

.authorizeRequests()

.antMatchers(

"/v1/",

"/token/",

"/token/",

"/actuator/",

"/mobile/**").permitAll() // 以“/token”,“/actuator”,“/mobile”开头的,不需要用户进行身份验证

.anyRequest().authenticated()

.and().csrf().disable()

.apply(mobileSecurityConfigurer());

}

@Bean

public MobileSecurityConfigurer mobileSecurityConfigurer() {

return new MobileSecurityConfigurer();

}

@Getter

@Setter

public class MobileSecurityConfigurer extends SecurityConfigurerAdapter {

@Resource

private ObjectMapper objectMapper;

@Resource

private AuthenticationEventPublisher defaultAuthenticationEventPublisher;

@Resource

private AuthenticationSuccessHandler mobileLoginSuccessHandler;

@Resource

private PigxUserDetailsService userDetailsService;

@Override

public void configure(HttpSecurity http) {

// 创建 MobileAuthenticationFilter 实例

MobileAuthenticationFilter mobileAuthenticationFilter = new MobileAuthenticationFilter();

// 设置身份验证管理器,用于处理身份验证

mobileAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));

// 设置身份验证成功处理程序,用于成功登录后的处理

mobileAuthenticationFilter.setAuthenticationSuccessHandler(mobileLoginSuccessHandler);

// 设置事件发布器,通常用于发布身份验证事件

mobileAuthenticationFilter.setEventPublisher(defaultAuthenticationEventPublisher);

// 设置身份验证入口点,用于处理身份验证失败的情况

mobileAuthenticationFilter.setAuthenticationEntryPoint(new ResourceAuthExceptionEntryPoint(objectMapper));

// 创建 MobileAuthenticationProvider 实例

MobileAuthenticationProvider mobileAuthenticationProvider = new MobileAuthenticationProvider();

// 设置用户详细信息服务,用于获取用户信息以进行身份验证

mobileAuthenticationProvider.setUserDetailsService(userDetailsService);

// 将移动设备身份验证提供者添加到 Spring Security 配置

http.authenticationProvider(mobileAuthenticationProvider)

// 将移动设备身份验证过滤器添加到 Spring Security 配置,并放在用户名密码身份验证过滤器之后

.addFilterAfter(mobileAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

}

}

/*

Copyright (c) 2018-2025, lengleng All rights reserved.

Redistribution and use in source and binary forms, with or without

modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice,

this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright

notice, this list of conditions and the following disclaimer in the

documentation and/or other materials provided with the distribution.

Neither the name of the pig4cloud.com developer nor the names of its

contributors may be used to endorse or promote products derived from

this software without specific prior written permission.

Author: lengleng (wangiegie@gmail.com)

*/

package pro.nbbt.xulian.common.security.mobile;

import cn.hutool.http.HttpUtil;

import pro.nbbt.xulian.common.core.constant.SecurityConstants;

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.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 pro.nbbt.xulian.common.core.util.http.HttpKit;

import pro.nbbt.xulian.common.security.service.PigxUser;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

@author lengleng

@date 2018/1/9

手机号登录验证filter

*/

public class MobileAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

private static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";

@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 MobileAuthenticationFilter() {

super(new AntPathRequestMatcher(SecurityConstants.MOBILE_TOKEN_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();

String authUser = HttpKit.getRequestParametersJSON(request.getHeader("app")).toString();

logger.info("Authentication : " + authUser);

MobileAuthenticationToken mobileAuthenticationToken = new MobileAuthenticationToken(authUser);

setDetails(request, mobileAuthenticationToken);

Authentication authResult = null;

try {

authResult = this.getAuthenticationManager().authenticate(mobileAuthenticationToken);

logger.info("Authentication request authResult: " + authResult);

SecurityContextHolder.getContext().setAuthentication(authResult);

} catch (Exception failed) {

SecurityContextHolder.clearContext();

logger.info("Authentication request failed: " + failed.getMessage());

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,

MobileAuthenticationToken authRequest) {

authRequest.setDetails(authenticationDetailsSource.buildDetails(request));

}

}

/*

Copyright (c) 2018-2025, lengleng All rights reserved.

Redistribution and use in source and binary forms, with or without

modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice,

this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright

notice, this list of conditions and the following disclaimer in the

documentation and/or other materials provided with the distribution.

Neither the name of the pig4cloud.com developer nor the names of its

contributors may be used to endorse or promote products derived from

this software without specific prior written permission.

Author: lengleng (wangiegie@gmail.com)

*/

package pro.nbbt.xulian.common.security.mobile;

import org.springframework.security.core.context.SecurityContextHolder;

import pro.nbbt.xulian.common.security.component.PigxPreAuthenticationChecks;

import pro.nbbt.xulian.common.security.service.PigxUser;

import pro.nbbt.xulian.common.security.service.PigxUserDetailsService;

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.SpringSecurityMessageSource;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.security.core.userdetails.UserDetailsChecker;

/**

@author lengleng

@date 2018/8/5

手机登录校验逻辑

验证码登录、社交登录

*/

@Slf4j

public class MobileAuthenticationProvider implements AuthenticationProvider {

private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

private UserDetailsChecker detailsChecker = new PigxPreAuthenticationChecks();

@Getter

@Setter

private PigxUserDetailsService userDetailsService;

@Override

@SneakyThrows

public Authentication authenticate(Authentication authentication) {

MobileAuthenticationToken mobileAuthenticationToken = (MobileAuthenticationToken) authentication;

String principal = mobileAuthenticationToken.getPrincipal().toString();

UserDetails userDetails = userDetailsService.loadUserBySocial(principal);

if (userDetails == null) {

log.debug("Authentication failed: no credentials provided");

throw new BadCredentialsException(messages.getMessage(

"AbstractUserDetailsAuthenticationProvider.noopBindAccount",

"Noop Bind Account"));

}

// 检查账号状态

detailsChecker.check(userDetails);

MobileAuthenticationToken authenticationToken = new MobileAuthenticationToken(userDetails, userDetails.getAuthorities());

Authentication authentication2 = SecurityContextHolder.getContext().getAuthentication();

if (authentication2 != null && authentication2.getPrincipal() instanceof PigxUser) {

PigxUser user = (PigxUser) authentication2.getPrincipal();

// 进行后续操作

log.info("authenticationToken信息:{}", user.getBindFlag());

}

log.info("authenticationToken信息2:{}", authenticationToken.getPrincipal());

authenticationToken.setDetails(mobileAuthenticationToken.getDetails());

return authenticationToken;

}

@Override

public boolean supports(Class authentication) {

return MobileAuthenticationToken.class.isAssignableFrom(authentication);

}

}

/*

Copyright (c) 2018-2025, lengleng All rights reserved.

Redistribution and use in source and binary forms, with or without

modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice,

this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright

notice, this list of conditions and the following disclaimer in the

documentation and/or other materials provided with the distribution.

Neither the name of the pig4cloud.com developer nor the names of its

contributors may be used to endorse or promote products derived from

this software without specific prior written permission.

Author: lengleng (wangiegie@gmail.com)

*/

package pro.nbbt.xulian.admin.handler;

import cn.hutool.core.lang.UUID;

import cn.hutool.core.util.StrUtil;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;

import lombok.AllArgsConstructor;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;

import org.springframework.stereotype.Component;

import pro.nbbt.xulian.admin.api.dto.AuthUserDTO;

import pro.nbbt.xulian.admin.api.dto.UserInfo;

import pro.nbbt.xulian.admin.api.entity.SysUser;

import pro.nbbt.xulian.admin.service.SysUserService;

import pro.nbbt.xulian.common.core.constant.CommonConstants;

import pro.nbbt.xulian.common.core.util.http.HttpKit;

import java.util.Objects;

/**

@author bazinga

@date 2020/11/05

*/

@Slf4j

@Component("GOOGLE_APP")

@AllArgsConstructor

public class GoogleAppLoginHandler extends AbstractLoginHandler {

private final SysUserService sysUserService;

/**

手机原生登录 传入的 code 就是 第三方账号ID

@param code

@return

*/

@Override

public String identify(String code) {

return code;

}

/**

loginStr 获取用户信息

@param loginStr

@return

*/

@Override

public UserInfo info(String loginStr) {

return null;

}

/**

authUser 获取用户信息

@param authUser

@return

*/

@Override

public UserInfo infoApp(AuthUserDTO authUser) {

log.info("<><><><> 当前Google登录邮箱 <><><><> {}", authUser.getUsername());

log.info("<><><><> 当前Google邮箱 uuid <><><><> {}", authUser.getUuid());

// 供应商标记 新app会在header中添加供应商标记

String appType = authUser.getAppType();

log.info("当前Google登录时的appType为:{}", appType);

// log.info("用户authUser的基本信息=={}",authUser.toString());

SysUser user = sysUserService.getOne(Wrappers.query().lambda()

.eq(SysUser::getGoogleId, authUser.getUuid())

.eq(SysUser::getUserLevelCode, CommonConstants.USER_LEVEL_APP) // 目前只有app有第三方登录

.eq(SysUser::getClusterType, CommonConstants.CLUSTER_TYPE));

if (user == null) {

// 通过username 获取用户信息

SysUser user1 = null;

if (StrUtil.isNotBlank(authUser.getUsername())) {

user1 = sysUserService.getOne(Wrappers.query().lambda()

.eq(SysUser::getUsername, authUser.getUsername())

.eq(SysUser::getUserLevelCode, CommonConstants.USER_LEVEL_APP) // 目前只有app有第三方登录

.eq(SysUser::getClusterType, CommonConstants.CLUSTER_TYPE));

}

if (Objects.nonNull(user1) && Objects.isNull(user1.getGoogleId())) {

// 存在并且 第三方id 为空则修改

user1.setPassword(null);

user1.setGoogleId(authUser.getUuid());

sysUserService.updateById(user1);

return sysUserService.findUserInfo(user1);

} else if (Objects.isNull(user1)) {

if (StrUtil.isEmpty(authUser.getUuid())) {

return null;

}

// 账号不存在

user = new SysUser();

user.setUsername(StrUtil.isNotBlank(authUser.getUsername()) ? authUser.getUsername() : UUID.fastUUID().toString().replace(StrUtil.DASHED, StrUtil.EMPTY));

user.setGoogleId(authUser.getUuid());

user.setPassword(UUID.fastUUID().toString());

user.setNickname(authUser.getNickname());

user.setAvatar(authUser.getAvatar());

user.setLockFlag("0");

user.setClusterType(CommonConstants.CLUSTER_TYPE);

// 如果邮箱不为空,则设置为已绑定邮箱

// if (StrUtil.isNotBlank(authUser.getUsername())) {

// user.setBindFlag(1);

// }

user.setSupplierInfo(authUser.getAppType());

sysUserService.registerForApp(user);

} else {

return null;

}

if (user != null && StrUtil.isNotBlank(authUser.getUsername()) && !StringUtils.equalsIgnoreCase(user.getUsername(), authUser.getUsername())) {

user.setUsername(authUser.getUsername());

sysUserService.updateById(user);

}

}

// if(user != null && StrUtil.isNotBlank(authUser.getUsername()) && !StringUtils.equalsIgnoreCase(user.getUsername(),authUser.getUsername())){

// user.setUsername(authUser.getUsername());

// sysUserService.updateById(user);

// }

log.info("当前Google登录时的user为:{}", user);

return sysUserService.findUserInfo(user);

}

}

3.上述代码中核心在于

infoApp方法将googele账户,facebook,appid账户引入自有项目,创立自有app账号密码,前提是她登录使用的第三方账号密码能登录第三方平台。

苹果手机在什么情况下QQ会显示 离线 ( 为什么iphone登QQ到12点就离线了呢? )
潮起又潮落是哪首歌的歌词