Spring Boot 3.x
Spring Security 5.7
Spring Redis
MyBatis plus
前端 Vue
前言
公司 最近有個(gè)新項(xiàng)目 使用單點(diǎn)登錄 sso
百度了一圈 也沒(méi)怎么找到微信掃碼注冊(cè)的功能于是自己寫(xiě)
- 需求就是 手機(jī) + 密碼登錄
- 微信掃碼登錄
- 微信掃碼注冊(cè)
微信二維碼 登錄 和注冊(cè)二合一 具體實(shí)現(xiàn) 稍后我會(huì)說(shuō)
本教程將指導(dǎo)您如何使用Spring Boot和Spring Security 5.7來(lái)實(shí)現(xiàn)基于手機(jī)號(hào)密碼登錄的認(rèn)證。通過(guò)本教程,您將學(xué)習(xí)如何配置Spring
Security,創(chuàng)建自定義的用戶(hù)認(rèn)證邏輯,并且使用手機(jī)號(hào)和密碼進(jìn)行登錄。
準(zhǔn)備工作
添加核心依賴(lài)
在您的Spring Boot項(xiàng)目的pom.xml文件中添加Spring Security依賴(lài):
- weixin-java-mp
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--weixin-java-mp組件是一個(gè)基于Java語(yǔ)言開(kāi)發(fā)的微信公眾號(hào)開(kāi)發(fā)工具包,是WxJava SDK在微信公眾號(hào)場(chǎng)景的一個(gè)實(shí)現(xiàn)-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${weixin.version}</version>
</dependency>
- application.yml 部分代碼
wxopen:
openid: 開(kāi)放平臺(tái)網(wǎng)站openid
appid: 公眾號(hào)appid
secret: 公眾號(hào)憑據(jù)
key: 公眾號(hào)密鑰
# 掃碼成功回調(diào)地址 這是你的回調(diào)接口 /code 文章后面會(huì)說(shuō)
redirect_url: https://www.xxxx.com/code
配置實(shí)體類(lèi)
@Configuration
public class WxMpServiceConfiguration {
@Value("${wxopen.appid}")
private String appId;
@Value("${wxopen.openid}")
private String openid;
@Value("${wxopen.secret}")
private String appSecret;
@Value("${wxopen.redirect_url}")
private String redirectUrl;
/**
* 定義WxMpService bean
*/
@Bean
public WxMpService wxMpService() {
WxMpService wxMpService = new WxMpServiceImpl();
WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
config.setAppId(openid);
config.setOauth2redirectUri(redirectUrl);
config.setSecret(appSecret);
wxMpService.setWxMpConfigStorage(config);
return wxMpService;
}
}
}
開(kāi)始回歸正題 實(shí)現(xiàn)基于手機(jī)號(hào)密碼登錄
獲取微信授權(quán)二維碼 登錄 注冊(cè) 我都使用一個(gè)二維碼 無(wú)非就是加一個(gè) 標(biāo)識(shí)來(lái)區(qū)分是 登錄 還是注冊(cè) (mode)
/**
* @Desc 獲取微信授權(quán)二維碼 此接口只接收前端的請(qǐng)求(監(jiān)聽(tīng)I(yíng)P白名單)
* @Author Likefr
* @param mode (login) 登錄 | (register) 注冊(cè)
* @Date 2024/03/01 11:02
*/
@GetMapping("/qr/{mode}")
public ResultBean getAuthQr(@PathVariable("mode") String mode) {
if (mode == null) {
return ResultBean.error(ResultBeanEnum.BIND_ERROR);
}
Map<String, Object> map = new HashMap<>();
map.put("appid", weChatConfig.getOpenId());
map.put("scope", "snsapi_login");
String uuid = UUID.randomUUID().toString();
String backUrl = "https://xxxxx.com/wechat/code?handlerType=" + mode + "&uuid=" + uuid;
map.put("redirect_uri", backUrl);
map.put("state", uuid);
return ResultBean.success(map);
}
可能會(huì)問(wèn) backUrl 這個(gè)地址是哪個(gè)地址? 其實(shí)就是一個(gè) 我們springboot 的一個(gè)接口
@RequestMapping(value = "/code", method = {RequestMethod.GET, RequestMethod.POST})
這個(gè)接口 放在下面具體 往下滑 !
前端 發(fā)送 @GetMapping(“/qr/{mode}”) 接口 來(lái)獲取二維碼
比如 :
- 我請(qǐng)求 /qr/login 我就能獲取登錄的二維碼
- 我請(qǐng)求 /qr/register 我就能獲取注冊(cè)的二維碼
我這邊貼上部分代碼 前端需要引入才能 使用 new WxLogin
<script src="https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></script>
<script src="https://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
// springboot 后端接口返回 (@GetMapping("/qr/{mode}"))
data.data = return ResultBean.success(map);
var obj = new WxLogin({
self_redirect: false,
id: 'weixinLogin', // 需要顯示的容器id
appid: data.data.appid, // 公眾號(hào)appid wx*******
scope: data.data.scope, // 網(wǎng)頁(yè)默認(rèn)即可
redirect_uri: data.data.redirect_uri, // 授權(quán)成功后回調(diào)的url
state: data.data.state, // 可設(shè)置為簡(jiǎn)單的隨機(jī)數(shù)加session用來(lái)校驗(yàn)
style: 'black', // 提供"black"、"white"可選。二維碼的樣式
href: '' // 外部css文件url,需要https
})
return
}
<div id="weixinLogin"></div>
這樣 前端就會(huì)顯示二維碼
然后就是微信掃碼成功后重定向 也就是調(diào)用我們這個(gè)接口的回調(diào)
tips 還記得我們剛才 前端 調(diào)用的 @GetMapping(“/qr/{mode}”) 接口參數(shù)嗎
mode 這個(gè)參數(shù) 就是用來(lái)區(qū)分 我是用來(lái) 登錄 還是注冊(cè)(handlerType)
這點(diǎn)很重要 ?。?!
/**
* @Desc 該接口只接收微信回調(diào)請(qǐng)求獲取code
* @Author Likefr
* @Date 2024/2/28 16:05
*/
@RequestMapping(value = "/code", method = {RequestMethod.GET, RequestMethod.POST})
public void getCallBackCode(HttpServletRequest request, HttpServletResponse response) throws IOException, WxErrorException {
String code = request.getParameter("code"); //獲取code
String handlerType = request.getParameter("handlerType"); //獲取二維碼類(lèi)型
// 登錄
if ("login".equals(handlerType)) {
response.sendRedirect("https://www.xxxx.com/#/index?uuid=" + code);
// 注冊(cè)
} else if ("register".equals(handlerType)) {
response.sendRedirect("https://www.xxxx.com/login/#/?uuid=" + code);
}
}
如果是login 我跳到前端首頁(yè)
if ("login".equals(handlerType)) {
response.sendRedirect("https://www.xxxx.com/#/index?uuid=" + code);
} else if ("register".equals(handlerType)) {
response.sendRedirect(""https://www.xxxx.com/login/#/?uuid=" + code);
}
這段代碼 就是微信幫我們跳轉(zhuǎn)到前端 的具體某個(gè)頁(yè)面
如果是掃碼登錄response.sendRedirect("https://www.xxxx.com/#/login?uuid=" + code);
前端 vue框架 created 鉤子函數(shù) 里面判斷 鏈接 uuid=" + code 是否存在code 存在則 發(fā)送/wechat/login接口
// spring security 攔截 POST /wechat/login 請(qǐng)求
RequestMatcher matcher = new AntPathRequestMatcher("/wechat/login", "POST", true);
然后經(jīng)過(guò) WxQrAuthenticationFilter WxQrAuthenticationFilter 獲取 剛才 鏈接上uuid 字段 的code值 獲取到 uuid后 封裝一個(gè) Authentication authentication = new WxQrAuthenticationToken(userForm.getUuid()); 對(duì)象
在 調(diào)用 WxQrCodeAuthenticationProvider 最后認(rèn)證
以下是我的 security 微信掃碼登錄認(rèn)證器
/**
* @author Likefr
* 基于用戶(hù)名(手機(jī)號(hào))、密碼、驗(yàn)證碼的登錄攔截器配置類(lèi)
*/
public class WxQrLoginConfigurer extends AbstractHttpConfigurer<WxQrLoginConfigurer, HttpSecurity> {
@Override
public void configure(HttpSecurity builder) {
// 攔截 POST /login 請(qǐng)求
RequestMatcher matcher = new AntPathRequestMatcher("/wechat/login", "POST", true);
SecurityUserDetailsService userDetailService = builder.getSharedObject(ApplicationContext.class).getBean(SecurityUserDetailsService.class);
RedisTemplate redisTemplate = builder.getSharedObject(ApplicationContext.class).getBean(RedisTemplate.class);
WxMpService wxMpService = builder.getSharedObject(ApplicationContext.class).getBean(WxMpService.class);
AuthenticationManager localAuthManager = builder.getSharedObject(AuthenticationManager.class);
WxQrAuthenticationFilter filter = new WxQrAuthenticationFilter(matcher, localAuthManager);
filter.setAuthenticationSuccessHandler(new LoginSuccessHandler(userDetailService, redisTemplate));
filter.setAuthenticationFailureHandler(new LoginFailHandler());
// 務(wù)必注意這里與配置類(lèi)中聲明的先后順序
builder.authenticationProvider(new WxQrCodeAuthenticationProvider(userDetailService, redisTemplate, wxMpService))
.addFilterBefore(filter, AuthenticationTokenFilter.class);
}
}
@Slf4j
public class WxQrCodeAuthenticationProvider implements AuthenticationProvider {
private SecurityUserDetailsService userDetailsService;
private RedisTemplate<String, Object> redisTemplate;
private WxMpService wxMpService;
public WxQrCodeAuthenticationProvider(SecurityUserDetailsService userService, RedisTemplate redisTemplate, WxMpService wxMpService) {
this.userDetailsService = userService;
this.redisTemplate = redisTemplate;
this.wxMpService = wxMpService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WxQrAuthenticationToken authenticationToken = (WxQrAuthenticationToken) authentication;
// 獲取前端的
String uuid = authenticationToken.getUuid();
// 根據(jù)uuid 獲取微信用戶(hù) (核心)
SecutityUser userDetails = checkUuid(uuid);
if (Objects.isNull(userDetails)) {
throw new InternalAuthenticationServiceException(" 無(wú)效的 uuid!");
}
// 用戶(hù)狀態(tài)校驗(yàn)
if (!userDetails.isEnabled() || !userDetails.isAccountNonLocked() || !userDetails.isAccountNonExpired()) {
throw new LockedException("用戶(hù)已禁用,請(qǐng)聯(lián)系管理員啟用");
}
WxQrAuthenticationToken authenticationResult = new WxQrAuthenticationToken(userDetails, uuid, new ArrayList<>());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return WxQrAuthenticationToken.class.isAssignableFrom(authentication);
}
private SecutityUser checkUuid(String uuid) {
WxOAuth2AccessToken accessToken = null;
WxOAuth2UserInfo userInfo;
try {
accessToken = wxMpService.getOAuth2Service().getAccessToken(uuid);
userInfo = wxMpService.getOAuth2Service().getUserInfo(accessToken, "zh_CN");
} catch (WxErrorException e) {
throw new CredentialsExpiredException(e.getMessage().toString());
}
// 根據(jù)微信用戶(hù)id 查找你數(shù)據(jù)庫(kù)的用戶(hù) 這邊我不貼代碼了 很簡(jiǎn)單
SecutityUser userDetails = userDetailsService.loadUserByUsername(userInfo.getOpenid());
return userDetails;
}
}
/**
* 二維碼登錄攔截器
*/
public class WxQrAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public WxQrAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher, AuthenticationManager authenticationManager) {
super(requiresAuthenticationRequestMatcher, authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
LoginVo userForm = HttpRequestUtil.getBodyJson(request);
logger.info(userForm.toString());
Authentication authentication = new WxQrAuthenticationToken(userForm.getUuid());
// authenticate 會(huì)執(zhí)行 SecutityUserDetailsService
return this.getAuthenticationManager().authenticate(authentication);
}
}
基于手機(jī)號(hào) + 密碼的認(rèn)證登錄
/**
* @author Likefr
* 基于用戶(hù)名(手機(jī)號(hào))、驗(yàn)證碼的登錄攔截器配置類(lèi)
*/
public class PhoneLoginConfigurer extends AbstractHttpConfigurer<PhoneLoginConfigurer, HttpSecurity> {
@Override
public void configure(HttpSecurity builder) {
// 攔截 POST /login 請(qǐng)求
RequestMatcher matcher = new AntPathRequestMatcher("/phone/doLogin", "POST", true);
SecurityUserDetailsService userDetailService = builder.getSharedObject(ApplicationContext.class).getBean(SecurityUserDetailsService.class);
RedisTemplate redisTemplate = builder.getSharedObject(ApplicationContext.class).getBean(RedisTemplate.class);
AuthenticationManager localAuthManager = builder.getSharedObject(AuthenticationManager.class);
PhoneAuthenticationFilter filter = new PhoneAuthenticationFilter(matcher, localAuthManager);
filter.setAuthenticationSuccessHandler(new LoginSuccessHandler(userDetailService, redisTemplate));
filter.setAuthenticationFailureHandler(new LoginFailHandler());
// 委托認(rèn)證autheticate AuthenticationProvider
builder.authenticationProvider(new PhoneAuthenticationProvider(userDetailService, redisTemplate))
.addFilterBefore(filter, AuthenticationTokenFilter.class);
}
}
/**
* @author Likefr
* 基于用戶(hù)名(手機(jī)號(hào))、密碼的認(rèn)證處理器
*/
@Slf4j
public class PhoneAuthenticationProvider implements AuthenticationProvider {
private final SecurityUserDetailsService userDetailService;
private RedisTemplate<String, Object> redisTemplate;
public PhoneAuthenticationProvider(SecurityUserDetailsService userDetailService, RedisTemplate redisTemplate) {
this.userDetailService = userDetailService;
this.redisTemplate = redisTemplate;
}
/**
* 驗(yàn)證主邏輯
*/
/*
provider = class ProviderManager implements AuthenticationManager
result = provider.authenticate(authentication);
* */
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
PhoneAuthenticationToken authToken = (PhoneAuthenticationToken) authentication;
if (authToken.getPhone() == null) {
throw new UsernameNotFoundException("未輸入手機(jī)號(hào)");
}
if (Objects.isNull(authToken.getCredentials())) {
throw new BadCredentialsException("密碼不能為空");
}
// 加載用戶(hù)詳情
// log.info("authToken.getCredentials() {} {}", authToken.getCredentials(), authToken.getPhone());
SecutityUser userDetails = userDetailService.loadUserByUsername(authToken.getPhone());
if (Objects.isNull(userDetails.getSysUser().getPassword())) {
throw new BadCredentialsException("當(dāng)前用戶(hù)未設(shè)置密碼");
}
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
if (!bCryptPasswordEncoder.matches(authToken.getCredentials(), userDetails.getSysUser().getPassword())) {
throw new BadCredentialsException("用戶(hù)名或密碼錯(cuò)誤,請(qǐng)重新輸入");
}
// 檢查用戶(hù)狀態(tài)
if (!userDetails.isEnabled() || !userDetails.isAccountNonLocked() || !userDetails.isAccountNonExpired()) {
throw new LockedException("用戶(hù)處于禁用狀態(tài),請(qǐng)聯(lián)系管理員啟用");
}
// 生成 JWT 并存儲(chǔ)
return new PhoneAuthenticationToken(userDetails, authToken.getCredentials(), authToken.getAuthorities());
}
/**
* 當(dāng)類(lèi)型為PasswordAuthenticationToken的認(rèn)證實(shí)體進(jìn)入時(shí)才走此Provider
*/
//
@Override
public boolean supports(Class<?> authentication) {
return PhoneAuthenticationToken.class.isAssignableFrom(authentication);
}
/**
* @author Likefr
* 用戶(hù)名密碼登錄攔截器
* 主要處理兩個(gè)事情 1 @params request 提取前端 提交的表單信息
* 2 然后封裝成 基于 手機(jī)號(hào) 驗(yàn)證碼 實(shí)現(xiàn)的Authentication 對(duì)象
* retuerns Authentication
*/
public class PhoneAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public PhoneAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher, AuthenticationManager authenticationManager) {
super(requiresAuthenticationRequestMatcher, authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
LoginVo userForm = HttpRequestUtil.getBodyJson(request);
Authentication authentication = new PhoneAuthenticationToken(userForm.getNickname(), userForm.getPassword());
// authenticate委托認(rèn)證 交由 DaoAuthenticationProvider
return this.getAuthenticationManager().authenticate(authentication);
}
}
增加 Token 攔截器配置
/**
* Token攔截器配置類(lèi)
* TokenAuthenticationFilter 負(fù)責(zé)處理我們攜帶了jwt token的請(qǐng)求。認(rèn)證工作主要由它負(fù)責(zé)。
*/
public class TokenAuthenticationConfigurer extends AbstractHttpConfigurer<TokenAuthenticationConfigurer, HttpSecurity> {
@Override
public void configure(HttpSecurity builder) {
RedisTemplate RedisTemplate = builder.getSharedObject(ApplicationContext.class).getBean(RedisTemplate.class);
AccessDeniedHandler accessDeniedHandler = builder.getSharedObject(ApplicationContext.class).getBean(AccessDeniedHandler.class);
builder.addFilterBefore(new AuthenticationTokenFilter(RedisTemplate, accessDeniedHandler), UsernamePasswordAuthenticationFilter.class);
}
}
JWT 攔截處理實(shí)現(xiàn)
/**
* JWT Token認(rèn)證攔截器 每次請(qǐng)求接口會(huì)先解析jwt
* 用戶(hù) 判斷是否存在token
* token 是否有效
* token 是否過(guò)期
*/
public class AuthenticationTokenFilter extends OncePerRequestFilter {
// token 存在redis 里邊 不存在則已退出登錄
private RedisTemplate redisTemplate;
public AuthenticationTokenFilter(RedisTemplate redisTemplate,
AccessDeniedHandler accessDeniedHandler) {
this.redisTemplate = redisTemplate;
this.accessDeniedHandler = accessDeniedHandler;
}
private AccessDeniedHandler accessDeniedHandler;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
// 未登錄 如果字符串不為 null 值,并且不含有空白字符序列,并且字符序列的長(zhǎng)度大于 0 ,則返回 true
if (!StringUtils.hasText(token)) {
filterChain.doFilter(request, response);
return;
}
token = token.replace("bearer ", "");
// 判斷jwt 是否過(guò)期
Boolean expire = JWTUtils.isExpire(token);
if (expire == null) {
accessDeniedHandler.handle(request, response, new AccessDeniedException(ResultBeanEnum.JWT_ERROR.getMeassage()));
return;
}
if (expire) {
// token 過(guò)期
filterChain.doFilter(request, response);
return;
}
Boolean redisJwtExists = redisTemplate.hasKey("loginToken:" + token);
SecutityUser userPojo = JWTUtils.checkToken(token);
if (userPojo != null && redisJwtExists) {
// 將解析的jwt 寫(xiě)入上下文 以供全局使用
PhoneAuthenticationToken authenticationToken = new PhoneAuthenticationToken(userPojo, token, userPojo.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// todo 實(shí)現(xiàn)jwt 無(wú)感續(xù)簽
// redisCacheUtil.setExpire(TokenConstant.TOKEN_REDIS_PREFIX + token, TokenConstant.TOKEN_EXPIRE_TIME, TokenConstant.TOKEN_EXPIRE_TIME_UNIT);
}
filterChain.doFilter(request, response);
}
}
接下來(lái)就是最重要的配置 security了 將我們前面的攔截器 配置進(jìn)去
/**
* @version: java version 17
* @Author: Likefr
* @description:
* @date: 2024-01-20 11:44
*/
@Configuration
/*
* 從Spring Security 4.0開(kāi)始,不推薦使用@EnableWebMvcSecurity 。 replace是@EnableWebSecurity將決定添加基于類(lèi)path的Spring MVCfunction。
為了使Spring Security與Spring MVC集成,將 @EnableWebSecurity 注釋 添加 到您的configuration中。
* */
@EnableWebSecurity
public class WebSecutityConfig {
@Autowired
private LogoutSuccessHanlder logoutHandler;
@Autowired
private LoginFailHandler loginFailHandler;
@Autowired
AuthenticationExceptionHandler authenticationFailHandler;
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//關(guān)閉csrf
http.csrf().disable()
//自定義登錄接口 禁用 FormLoginConfigurer 默認(rèn)登錄實(shí)現(xiàn)
.formLogin().disable()
.exceptionHandling().authenticationEntryPoint(authenticationFailHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER).and()
.authorizeHttpRequests(authorize -> authorize
// 放行手機(jī)號(hào)密碼登錄接口
.requestMatchers("/phone/doLogin").permitAll()
// 放行用戶(hù)注冊(cè)接口
.requestMatchers("/sysUser/register").permitAll()
// 微信掃碼回調(diào)(這個(gè)很重要)
.requestMatchers("/code/**").permitAll()
.requestMatchers("/wechat/**").permitAll()
.anyRequest().authenticated())
.formLogin().failureHandler(loginFailHandler).and()
.logout().logoutUrl("/logout").logoutSuccessHandler(logoutHandler).and()
// 務(wù)必注意這里的先后順序,否則會(huì)報(bào)NULL異常
// token 攔截 我使用的是jwt
.apply(new TokenAuthenticationConfigurer()).and()
// 手機(jī)號(hào)登錄攔截器
.apply(new PhoneLoginConfigurer()).and()
// 微信掃碼登錄攔截器
.apply(new WxQrLoginConfigurer());
return http.build();
}
}
大致說(shuō)下上面代碼
需要注意的是放行登錄 注冊(cè) 接口 還有獲取微信二維碼的接口 如果不放行 你的登錄或者注冊(cè) 沒(méi)法訪問(wèn) (
沒(méi)登陸當(dāng)然無(wú)法訪問(wèn)了,熟悉security的都知道)
// token 攔截 我使用的是jwt
.apply(new TokenAuthenticationConfigurer()).and()
// 手機(jī)號(hào)登錄攔截器
.apply(new PhoneLoginConfigurer()).and()
// 微信掃碼登錄攔截器
.apply(new WxQrLoginConfigurer());
這三行代碼是在配置 Spring Security 的過(guò)濾器鏈中應(yīng)用了三個(gè)自定義的配置器(configurer)。這些配置器定義了特定的認(rèn)證方式或者處理特定類(lèi)型的認(rèn)證請(qǐng)求
我簡(jiǎn)單說(shuō)下:
-
.apply(new TokenAuthenticationConfigurer())
: 這一行代碼聲明了一個(gè)名為TokenAuthenticationConfigurer
的配置器,這個(gè)配置器我實(shí)現(xiàn)了攔截基于令牌(JWT)的身份驗(yàn)證 -
.apply(new PhoneLoginConfigurer())
: 這一行代碼聲明了一個(gè)名為PhoneLoginConfigurer
的配置器,準(zhǔn)確的說(shuō)是手機(jī)號(hào)
密碼登錄這個(gè)配置器用于配置手機(jī)登錄認(rèn)證方式,包含手機(jī)號(hào) + 密碼登錄 -
.apply(new WxQrLoginConfigurer())
: 這一行代碼應(yīng)用了一個(gè)名為WxQrLoginConfigurer
的配置器。根據(jù)名稱(chēng)應(yīng)該可以知道,是一個(gè)前端掃碼微信二維碼登錄,
這個(gè)配置了微信掃碼登錄認(rèn)證方式,涉及對(duì)接微信授權(quán)。
這三個(gè)配置器的先后順序非常重要,因?yàn)樗鼈兛赡軙?huì)相互依賴(lài)或者有先后執(zhí)行的邏輯,說(shuō)這里的順序可能影響到程序的正確性。如果這些配置器之間有先后順序的要求,而沒(méi)有按照要求配置,就有可能出現(xiàn)空指針異常(NULL
異常)或其他配置錯(cuò)誤。在使用這些配置器時(shí)務(wù)必注意它們的先后順序
再就是微信掃碼注冊(cè)實(shí)現(xiàn) 還記得 我們前端調(diào)用的這個(gè)接口嗎?
@GetMapping("/qr/{mode}")
當(dāng)mode = register時(shí) 我會(huì)重定向到前端登錄頁(yè)
response.sendRedirect("https://www.xxxx.com/login/#/?uuid=" + code);
就是我會(huì)在注冊(cè)頁(yè)Login.vue created 鉤子函數(shù)里面
判斷 ?uuid=" + code 是否存在 存在的話(huà) 就說(shuō)明是用戶(hù)正在注冊(cè) 讓用戶(hù) 輸入 手機(jī)號(hào) 密碼 和驗(yàn)證碼注冊(cè)
注冊(cè)接口 如下文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-846032.html
具體參數(shù),其實(shí)無(wú)非就是你注冊(cè)的手機(jī)號(hào) 還有密碼 這個(gè)根據(jù)你的業(yè)務(wù)去寫(xiě)
loginVo 實(shí)體類(lèi)包含uuid這個(gè)字段 這個(gè)u id的值其實(shí)就是微信掃碼登錄成功返回 code
** response.sendRedirect(“https://www.xxxx.com/login/#/?uuid=” + code);**
所以前端要解析出 uuid 并在注冊(cè)的時(shí)候把這個(gè)值傳過(guò)來(lái),獲取微信用戶(hù)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-846032.html
public ResultBean addUser(LoginVo loginVo) {
// 手機(jī)驗(yàn)證碼是否為空
if (StringUtils.isEmpty(loginVo.getCode())) {
return ResultBean.error(ResultBeanEnum.CODE_NOTNULL_ERROR); // 驗(yàn)證碼為空
}
// 校驗(yàn)驗(yàn)證碼
String redisCode = (String) redisTemplate.opsForValue().get(RedisKeyConstant.SMSCODE.cacheKey + loginVo.getPhone());
if (!loginVo.getCode().equals(redisCode)) {
return ResultBean.error(ResultBeanEnum.CODE_ERROR);
}
QueryWrapper<SysUser> wrapper = new QueryWrapper<SysUser>();
// 判斷手機(jī)號(hào)是否注冊(cè)過(guò)
wrapper.eq("phone", loginVo.getPhone());
long count = count(wrapper);
if (count > 0) {
return ResultBean.error(ResultBeanEnum.PHONE_OTHER_ACCOUNTS_ERROR);
}
// 獲取微信id
WxOAuth2AccessToken accessToken = null;
WxOAuth2UserInfo userInfo;
try {
log.info("loginVo.getUuid():{}", loginVo.getUuid());
accessToken = wxMpService.getOAuth2Service().getAccessToken(loginVo.getUuid());
// log.info("accessToken- {}", accessToken);
userInfo = wxMpService.getOAuth2Service().getUserInfo(accessToken, "zh_CN");
} catch (WxErrorException e) {
throw new CredentialsExpiredException(e.getMessage().toString());
}
wrapper = new QueryWrapper<SysUser>();
wrapper.eq("openid", userInfo.getOpenid());
// 判斷微信是否已經(jīng)綁定了其他手機(jī)號(hào)
long wxCount = count(wrapper);
if (wxCount > 0) {
return ResultBean.error(ResultBeanEnum.WECHAT_OTHER_ACCOUNTS_ERROR);
}
SysUser sysUser = new SysUser();
sysUser.setNickname(loginVo.getPhone());
sysUser.setPhone(loginVo.getPhone());
sysUser.setOpenid(userInfo.getOpenid());
sysUser.setCreateTime(LocalDateTime.now());
sysUser.setState(true);
if (!ObjectUtils.isEmpty(loginVo.getPassword())) {
String password = new BCryptPasswordEncoder().encode(loginVo.getPassword());
sysUser.setPassword(password);
}
boolean save = save(sysUser);
if (save) {
// 注冊(cè)成功
return ResultBean.success();
}
return ResultBean.error(ResultBeanEnum.USER_ADD_ERROR);
}
到了這里,關(guān)于使用Spring Boot Security 實(shí)現(xiàn)多認(rèn)證 手機(jī)號(hào)登錄 微信掃碼登錄 微信掃碼注冊(cè)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!