提交 ba823b5e 编写于 作者: J jiojio

Introducing new features. oauth2.0 验证码登录

上级 7d63b141
......@@ -17,7 +17,7 @@
package com.pig4cloud.pig.auth.config;
import com.pig4cloud.pig.auth.converter.CustomAccessTokenConverter;
import com.pig4cloud.pig.auth.grant.ResourceOwnerPhoneTokenGranter;
import com.pig4cloud.pig.common.security.grant.ResourceOwnerPhoneTokenGranter;
import com.pig4cloud.pig.common.core.constant.CacheConstants;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.security.component.PigWebResponseExceptionTranslator;
......@@ -92,8 +92,9 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
// 获取默认授权类型
TokenGranter tokenGranter = endpoints.getTokenGranter();
ArrayList<TokenGranter> tokenGranters = new ArrayList<>(Arrays.asList(tokenGranter));
ResourceOwnerPhoneTokenGranter resourceOwnerPhoneTokenGranter = new ResourceOwnerPhoneTokenGranter(authenticationManager,
endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory());
ResourceOwnerPhoneTokenGranter resourceOwnerPhoneTokenGranter = new ResourceOwnerPhoneTokenGranter(
authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory());
tokenGranters.add(resourceOwnerPhoneTokenGranter);
CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(tokenGranters);
endpoints.tokenGranter(compositeTokenGranter);
......
......@@ -16,7 +16,7 @@
package com.pig4cloud.pig.auth.config;
import com.pig4cloud.pig.auth.grant.PhoneAuthenticationProvider;
import com.pig4cloud.pig.common.security.grant.PhoneAuthenticationProvider;
import com.pig4cloud.pig.common.security.handler.FormAuthenticationFailureHandler;
import com.pig4cloud.pig.common.security.handler.SsoLogoutSuccessHandler;
import lombok.AllArgsConstructor;
......@@ -50,9 +50,8 @@ public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
@SneakyThrows
protected void configure(HttpSecurity http) {
http.authenticationProvider(phoneAuthenticationProvider())
.formLogin().loginPage("/token/login").loginProcessingUrl("/token/form")
.failureHandler(authenticationFailureHandler()).and().logout()
http.authenticationProvider(phoneAuthenticationProvider()).formLogin().loginPage("/token/login")
.loginProcessingUrl("/token/form").failureHandler(authenticationFailureHandler()).and().logout()
.logoutSuccessHandler(logoutSuccessHandler()).deleteCookies("JSESSIONID").invalidateHttpSession(true)
.and().authorizeRequests().antMatchers("/token/**", "/actuator/**", "/mobile/**").permitAll()
.anyRequest().authenticated().and().csrf().disable();
......@@ -87,7 +86,6 @@ public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
/**
* 支持SSO 退出
*
* @return LogoutSuccessHandler
*/
@Bean
......@@ -98,7 +96,6 @@ public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
/**
* https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-updated
* Encoded password does not look like BCrypt
*
* @return PasswordEncoder
*/
@Bean
......
......@@ -56,6 +56,10 @@ public interface SecurityConstants {
* grant_type
*/
String REFRESH_TOKEN = "refresh_token";
/**
* 手机号登录
*/
String PHONE = "phone";
/**
......@@ -120,4 +124,9 @@ public interface SecurityConstants {
*/
long CODE_TIME = 60;
/**
* 验证码长度
*/
String CODE_SIZE = "6";
}
......@@ -33,14 +33,9 @@ public enum LoginTypeEnum {
PWD("PWD", "账号密码登录"),
/**
* QQ登录
* 验证码登录
*/
QQ("QQ", "QQ登录"),
/**
* 微信登录
*/
WECHAT("WX", "微信登录");
SMS("SMS", "验证码登录");
/**
* 类型
......
package com.pig4cloud.pig.auth.grant;
package com.pig4cloud.pig.common.security.grant;
import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pig.common.security.service.PigUserDetailsServiceImpl;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
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 org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
......@@ -16,13 +20,32 @@ import org.springframework.security.crypto.password.PasswordEncoder;
* @since 2021-09-14
*/
@Slf4j
public class PhoneAuthenticationProvider implements AuthenticationProvider {
public class PhoneAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
@Setter
private UserDetailsService userDetailsService;
@Setter
private PasswordEncoder passwordEncoder;
/**
* 校验 请求信息userDetails
* @param userDetails 用户信息
* @param authentication 认证信息
* @throws AuthenticationException
*/
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
......@@ -34,29 +57,26 @@ public class PhoneAuthenticationProvider implements AuthenticationProvider {
// 手机号
String phone = authentication.getName();
// 验证码/密码
// 验证码模式 自己去实现验证码检验
// 这里的code指的是密码
String code = authentication.getCredentials().toString();
UserDetails userDetails = ((PigUserDetailsServiceImpl) userDetailsService).loadUserByPhone(phone);
String password = userDetails.getPassword();
boolean matches = passwordEncoder.matches(code, password);
if (!matches) {
throw new BeanCreationException("Bad credentials");
if (StrUtil.equals(phone, "17034642999")) {
throw new UsernameNotFoundException(phone);
}
String code = authentication.getCredentials().toString();
UserDetails userDetails = ((PigUserDetailsServiceImpl) userDetailsService).loadUserByPhone(phone);
PhoneAuthenticationToken token = new PhoneAuthenticationToken(userDetails);
token.setDetails(authentication.getDetails());
return token;
}
@Override
protected UserDetails retrieveUser(String phone, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.isAssignableFrom(PhoneAuthenticationToken.class);
}
}
package com.pig4cloud.pig.auth.grant;
package com.pig4cloud.pig.common.security.grant;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
......@@ -10,7 +10,7 @@ import org.springframework.security.core.userdetails.UserDetails;
*/
public class PhoneAuthenticationToken extends AbstractAuthenticationToken {
private Object principal;
private final Object principal;
// 验证码/密码
private String code;
......
package com.pig4cloud.pig.auth.grant;
package com.pig4cloud.pig.common.security.grant;
import cn.hutool.core.util.StrUtil;
import org.springframework.security.authentication.AbstractAuthenticationToken;
......@@ -27,15 +27,13 @@ public class ResourceOwnerPhoneTokenGranter extends AbstractTokenGranter {
private final AuthenticationManager authenticationManager;
public ResourceOwnerPhoneTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
}
protected ResourceOwnerPhoneTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
......@@ -62,13 +60,13 @@ public class ResourceOwnerPhoneTokenGranter extends AbstractTokenGranter {
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = authenticationManager.authenticate(userAuth);
} catch (AccountStatusException ase) {
//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
}
catch (AccountStatusException | BadCredentialsException ase) {
// covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
} catch (BadCredentialsException e) {
// If the phone/code are wrong the spec says we should send 400/invalid grant
throw new InvalidGrantException(e.getMessage());
}
// If the phone/code are wrong the spec says we should send 400/invalid grant
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate user: " + phone);
}
......@@ -76,4 +74,5 @@ public class ResourceOwnerPhoneTokenGranter extends AbstractTokenGranter {
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
......@@ -62,7 +62,6 @@ public class PigUserDetailsServiceImpl implements UserDetailsService {
/**
* 用户密码登录
*
* @param username 用户名
* @return
*/
......@@ -84,7 +83,6 @@ public class PigUserDetailsServiceImpl implements UserDetailsService {
/**
* 手机号码登录
*
* @param phone 手机号码
* @return 用户信息
*/
......@@ -96,7 +94,6 @@ public class PigUserDetailsServiceImpl implements UserDetailsService {
/**
* 构建userdetails
*
* @param result 用户信息
* @return UserDetails
*/
......
......@@ -70,7 +70,8 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory<Obje
// 刷新token,手机号登录(也可以这里进行校验) 直接向下执行
String grantType = request.getQueryParams().getFirst("grant_type");
if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType) || StrUtil.equals(SecurityConstants.PHONE, grantType)) {
if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)
|| StrUtil.equals(SecurityConstants.PHONE, grantType)) {
return chain.filter(exchange);
}
......@@ -80,7 +81,8 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory<Obje
if (!isIgnoreClient) {
checkCode(request);
}
} catch (Exception e) {
}
catch (Exception e) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.PRECONDITION_REQUIRED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
......@@ -92,7 +94,8 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory<Obje
DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
monoSink.success(dataBuffer);
} catch (JsonProcessingException jsonProcessingException) {
}
catch (JsonProcessingException jsonProcessingException) {
log.error("对象输出异常", jsonProcessingException);
monoSink.error(jsonProcessingException);
}
......
......@@ -40,7 +40,6 @@ public interface RemoteUserService {
/**
* 通过用户名查询用户、角色信息
*
* @param username 用户名
* @param from 调用标志
* @return R
......@@ -50,26 +49,15 @@ public interface RemoteUserService {
/**
* 通过手机号码查询用户、角色信息
*
* @param phone 手机号码
* @param from 调用标志
* @return R
*/
@GetMapping("/user/infoByPhone/{phone}")
@GetMapping("/mobile/{phone}")
R<UserInfo> infoByPhone(@PathVariable("phone") String phone, @RequestHeader(SecurityConstants.FROM) String from);
/**
* 通过社交账号查询用户、角色信息
*
* @param inStr appid@code
* @return
*/
@GetMapping("/social/info/{inStr}")
R<UserInfo> social(@PathVariable("inStr") String inStr);
/**
* 根据部门id,查询对应的用户 id 集合
*
* @param deptIds 部门id 集合
* @param from 调用标志
* @return 用户 id 集合
......
......@@ -39,7 +39,6 @@ public class RemoteUserServiceFallbackImpl implements RemoteUserService {
/**
* 通过用户名查询用户、角色信息
*
* @param username 用户名
* @param from 内外标志
* @return R
......@@ -52,7 +51,6 @@ public class RemoteUserServiceFallbackImpl implements RemoteUserService {
/**
* 通过手机号码查询用户、角色信息
*
* @param phone 手机号码
* @param from 调用标志
* @return R
......@@ -63,18 +61,6 @@ public class RemoteUserServiceFallbackImpl implements RemoteUserService {
return null;
}
/**
* 通过社交账号查询用户、角色信息
*
* @param inStr appid@code
* @return
*/
@Override
public R<UserInfo> social(String inStr) {
log.error("feign 查询用户信息失败:{}", inStr, cause);
return null;
}
@Override
public R<List<Integer>> listUserIdByDeptIds(Set<Integer> deptIds, String from) {
log.error("feign 根据部门ids查询用户Id集合失败:{}", deptIds, cause);
......
package com.pig4cloud.pig.admin.controller;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.pig4cloud.pig.admin.api.entity.SysUser;
import com.pig4cloud.pig.admin.service.MobileService;
import com.pig4cloud.pig.admin.service.SysUserService;
import com.pig4cloud.pig.common.core.util.R;
import com.pig4cloud.pig.common.security.annotation.Inner;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lengleng
* @date 2021/9/16 移动端登录
*/
@RestController
@AllArgsConstructor
@RequestMapping("/mobile")
@Api(value = "mobile", tags = "手机管理模块")
public class MobileController {
private final MobileService mobileService;
private final SysUserService userService;
@Inner(value = false)
@GetMapping("/{mobile}")
public R sendSmsCode(@PathVariable String mobile) {
return mobileService.sendSmsCode(mobile);
}
/**
* 获取指定用户全部信息
* @param phone 手机号
* @return 用户信息
*/
@Inner
@GetMapping("/{phone}")
public R infoByPhone(@PathVariable String phone) {
SysUser user = userService.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getPhone, phone));
if (user == null) {
return R.failed(String.format("用户信息为空 %s", phone));
}
return R.ok(userService.getUserInfo(user));
}
}
......@@ -94,20 +94,6 @@ public class UserController {
return R.ok(userService.getUserInfo(user));
}
/**
* 获取指定用户全部信息
* @return 用户信息
*/
@Inner
@GetMapping("/infoByPhone/{phone}")
public R infoByPhone(@PathVariable String phone) {
SysUser user = userService.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getPhone, phone));
if (user == null) {
return R.failed(String.format("用户信息为空 %s", phone));
}
return R.ok(userService.getUserInfo(user));
}
/**
* 根据部门id,查询对应的用户 id 集合
* @param deptIds 部门id 集合
......
/*
* 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 com.pig4cloud.pig.admin.service;
import com.pig4cloud.pig.common.core.util.R;
/**
* @author lengleng
* @date 2018/11/14
*/
public interface MobileService {
/**
* 发送手机验证码
* @param mobile mobile
* @return code
*/
R<Boolean> sendSmsCode(String mobile);
}
/*
* 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 com.pig4cloud.pig.admin.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.pig4cloud.pig.admin.api.entity.SysUser;
import com.pig4cloud.pig.admin.mapper.SysUserMapper;
import com.pig4cloud.pig.common.core.constant.CacheConstants;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.core.constant.enums.LoginTypeEnum;
import com.pig4cloud.pig.common.core.util.R;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author lengleng
* @date 2018/11/14
* <p>
* 手机登录相关业务实现
*/
@Slf4j
@Service
@AllArgsConstructor
public class MobileServiceImpl implements MobileService {
private final RedisTemplate redisTemplate;
private final SysUserMapper userMapper;
/**
* 发送手机验证码 TODO: 调用短信网关发送验证码,测试返回前端
* @param mobile mobile
* @return code
*/
@Override
public R<Boolean> sendSmsCode(String mobile) {
List<SysUser> userList = userMapper
.selectList(Wrappers.<SysUser>query().lambda().eq(SysUser::getPhone, mobile));
if (CollUtil.isEmpty(userList)) {
log.info("手机号未注册:{}", mobile);
return R.ok(Boolean.FALSE, "手机号未注册");
}
Object codeObj = redisTemplate.opsForValue()
.get(CacheConstants.DEFAULT_CODE_KEY + LoginTypeEnum.SMS.getType() + StringPool.AT + mobile);
if (codeObj != null) {
log.info("手机号验证码未过期:{},{}", mobile, codeObj);
return R.ok(Boolean.FALSE, "验证码发送过频繁");
}
String code = RandomUtil.randomNumbers(Integer.parseInt(SecurityConstants.CODE_SIZE));
log.debug("手机号生成验证码成功:{},{}", mobile, code);
redisTemplate.opsForValue().set(
CacheConstants.DEFAULT_CODE_KEY + LoginTypeEnum.SMS.getType() + StringPool.AT + mobile, code,
SecurityConstants.CODE_TIME, TimeUnit.SECONDS);
return R.ok(Boolean.TRUE, code);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册