PigUserDetailsService 代码地址与接口文档看总目录:【学习笔记】记录冷冷-pig项目的学习过程,大概包括Authorization Server、springcloud、Mybatis Plus~~~_清晨敲代码的博客-CSDN博客
终于结束从零搭建springcloud的部分了,目前也仅仅是学习了最最基本的逻辑,同时包含了开发系统的一些基本的逻辑。接下来就按照 pig 文档将其余基本的内容再熟悉一下,看一遍和写一遍真的不一样呐~~~
那接下来就一小模块一小模块的学习啦,加油吧少年!
本文及以后的文章还是基于前面的No6系列文章开发的,可以看之前文章顶部的内容总结,简单了解详情~
目录
A1.手机号验证码登录模式
B1.步骤
首先,需要提供一个根据手机号码获取验证码的接口,这个接口写在 pig-upms-biz 里面就行。而且上一篇在 pig-gateway 网关已经有了校验验证码过滤器,所以也不需要编写验证码校验了,用一个就行。
然后在 pig-auth 模块里面继承一套基于 OAuth2ResourceOwnerBaseAuthenticationXXXX 的 converter、token、provider 类,是用于 OAuth2 用户认证的,然后在修改一下用户认证里面的密码校验逻辑,对于短信验证登录模式是不校验密码的~
B2.编码
1.在 pig-upms-biz 模块里面添加根据手机号码获取验证码的接口,其中判断手机号码是否存在账号,并且判断是否有未过期校验码,最终将以手机号码为key,验证码为value保存到redis里面,并返回。【返回前要调用发送短信验证码逻辑发送短信~,这里我就不加了】;
2.然后上面的controller类中增加一个根据手机号码获取用户信息的方法。
【用于 UserDetailsService 里面获取用户信息】
3.在 pig-upms-api 模块里面的 RemoteUserService 接口中新建一个远程调用接口方法,用来远程调用上面的方法;
4.在 pig-commin-security 里面新建一个实现 PigUserDetailsService 接口的类,用来根据手机号码拿到用户信息,其中会用到上面的远程接口方法;并且将他添加到 spring 容器中;
5.修改 PigDaoAuthenticationProvider#retrieveUser() 方法,原来默认就是拿到排序最高的,现在需要根据 grantType 判断具体用哪一个 UserDetailsService 来获取用户信息。并且一定要修改密码验证方法,在里面加入判断,如果是短信登录模式模式就不需要校验密码,因为短信登录模式没有密码嘛~
6.在 pig-auth 模块里面继承一套基于 OAuth2ResourceOwnerBaseAuthenticationXXXX 的 converter、token、provider 类,完全按照密码模式登录的编写就好;
7.修改 AuthorizationServerConfiguration 类,将短信验证码模式的 converter 和 provider 添加到配置里面;
8.在数据库中找到 sys_oauth_client_details 表,给使用的客户端账号 authorized_grant_types 里面加上短信验证登录的标识,只有有该登录模式的标识才能够使用该登录模式。
-
//1.在 pig-upms-biz 模块里面添加根据手机号码获取验证码的接口,其中判断手机号码是否存在账号,并且判断是否有未过期校验码,最终将以手机号码为key,验证码为value保存到redis里面,并返回。【返回前要调用发送短信验证码逻辑发送短信~,这里我就不加了】;
-
-
@RestController
-
@AllArgsConstructor
-
@RequestMapping("/app")
-
public
class
AppController {
-
-
private
final AppService appService;
-
-
/**
-
* @Description: 根据手机号码获取验证码【注意生产环境记得将返回的code去掉~】
-
* @param: * @param mobile
-
* @return: com.pig4cloud.pig.common.core.util.R<java.lang.Boolean>
-
**/
-
@Inner(value = false)
-
@GetMapping("/{mobile}")
-
public R<Boolean>
sendSmsCode
(@PathVariable String mobile) {
-
return appService.sendSmsCode(mobile);
-
}
-
-
}
-
-
//因为用不到其他的 mapper ,所以就不用继承 mps 自带的 service 了
-
@Slf4j
-
@Service
-
@RequiredArgsConstructor
-
public
class
AppServiceImpl
implements
AppService {
-
-
private
final RedisTemplate redisTemplate;
-
-
private
final SysUserMapper userMapper;
-
-
@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,
"手机号未注册:{"+mobile+
"}");
-
}
-
-
//根据手机号码从 redis 里面获取 code
-
Object
codeObj
= redisTemplate.opsForValue().get(CacheConstants.DEFAULT_CODE_KEY + mobile);
-
//判断该手机号码是否有未失效的验证码,没有就直接返回
-
if (codeObj !=
null) {
-
log.info(
"手机号验证码未过期:{},{}", mobile, codeObj);
-
return R.ok(Boolean.FALSE,
"请勿频繁获取验证码");
-
}
-
-
//在这里生成 code
-
String
code
= RandomUtil.randomNumbers(Integer.parseInt(SecurityConstants.CODE_SIZE));
-
log.info(
"手机号生成验证码成功:{},{}", mobile, code);
-
-
//将手机号码为key,验证码为value保存到redis里面
-
redisTemplate.opsForValue()
-
.set(CacheConstants.DEFAULT_CODE_KEY + mobile, code, SecurityConstants.CODE_TIME, TimeUnit.SECONDS);
-
-
// todo 记得调用短信通道发送
-
return R.ok(Boolean.TRUE, code);
-
}
-
}
-
//2.然后上面的controller类中增加一个根据手机号码获取用户信息的方法。
-
-
@RestController
-
@AllArgsConstructor
-
@RequestMapping("/app")
-
public
class
AppController {
-
-
private
final AppService appService;
-
-
private
final SysUserService userService;
-
-
/**
-
* @Description: 获取指定用户全部信息
-
* @param: * @param phone
-
* @return: com.pig4cloud.pig.common.core.util.R<com.pig4cloud.pig.admin.api.vo.UserInfoVO>
-
**/
-
@Inner
-
@GetMapping("/info/{phone}")
-
public R<UserInfoVO>
infoByMobile
(@PathVariable String phone) {
-
SysUser
user
= userService.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getPhone, phone));
-
if (user ==
null) {
-
return R.failed(
"用户信息为空");
-
}
-
return R.ok(userService.getUserInfo(user));
-
}
-
-
}
-
//3.在 pig-upms-api 模块里面的 RemoteUserService 接口中新建一个远程调用接口方法,用来远程调用上面的方法;
-
-
@FeignClient(contextId = "remoteUserService", value = ServiceNameConstants.UMPS_SERVICE)
-
public
interface
RemoteUserService {
-
-
/**
-
* @Description: 通过手机号码查询用户、角色信息
-
* @param: * @param phone
-
* @param from
-
* @return: com.pig4cloud.pig.common.core.util.R<UserInfo>
-
**/
-
@GetMapping("/app/info/{phone}")
-
R<UserInfoVO>
infoByMobile
(@PathVariable("phone") String phone, @RequestHeader(SecurityConstants.FROM) String from);
-
-
-
}
-
//4.在 pig-commin-security 里面新建一个实现 PigUserDetailsService 接口的类,用来根据手机号码拿到用户信息,其中会用到上面的远程接口方法;并且将他添加到 spring 容器中;
-
-
@Slf4j
-
@RequiredArgsConstructor
-
public
class
PigAppUserDetailsServiceImpl
implements
PigUserDetailsService {
-
-
private
final RemoteUserService remoteUserService;
-
-
/**
-
* @Description: 手机号登录
-
* @param: * @param username
-
* @return: org.springframework.security.core.userdetails.UserDetails
-
**/
-
@Override
-
public UserDetails
loadUserByUsername
(String phone)
throws UsernameNotFoundException {
-
-
R<UserInfoVO> result = remoteUserService.infoByMobile(phone, SecurityConstants.FROM_SECRET_VALUE);
-
-
UserDetails
userDetails
= getUserDetails(result);
-
-
return userDetails;
-
}
-
}
-
-
//在 /resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 里面加上 PigAppUserDetailsServiceImpl 类
-
-
com.pig4cloud.pig.common.security.service.PigAppUserDetailsServiceImpl
-
//5.修改 PigDaoAuthenticationProvider#retrieveUser() 方法,原来默认就是拿到排序最高的,现在需要根据 grantType 判断具体用哪一个 UserDetailsService 来获取用户信息。并且一定要修改密码验证方法,在里面加入判断,如果是短信登录模式模式就不需要校验密码,因为短信登录模式没有密码嘛~
-
//密码模式登录的 service 用的是父类提供的,并且他的 Order 是最小的,因此不会影响其他模式的
-
-
@Slf4j
-
@RequiredArgsConstructor
-
public
class
PigAppUserDetailsServiceImpl
implements
PigUserDetailsService {
-
-
/**
-
* @Description: 是否支持此客户端校验
-
* @param: * @param grantType
-
* @return: boolean
-
**/
-
@Override
-
public
boolean
support
(String grantType) {
-
return SecurityConstants.APP.equals(grantType);
-
}
-
}
-
-
-
//修改此类的两个方法
-
public
class
PigDaoAuthenticationProvider
extends
AbstractUserDetailsAuthenticationProvider {
-
-
@Override
-
protected
void
additionalAuthenticationChecks
(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
-
// 短息验证码模式不用校验密码
-
String
grantType
= Optional.ofNullable(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest())
-
.get().getParameter(OAuth2ParameterNames.GRANT_TYPE);
-
if (StrUtil.equals(SecurityConstants.APP, grantType)) {
-
return;
-
}
-
-
if (authentication.getCredentials() ==
null) {
-
this.logger.debug(
"Failed to authenticate since no credentials provided");
-
throw
new
BadCredentialsException(
this.messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
-
}
else {
-
String
presentedPassword
= authentication.getCredentials().toString();
-
if (!
this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
-
this.logger.debug(
"Failed to authenticate since password does not match stored value");
-
throw
new
BadCredentialsException(
this.messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
-
}
-
}
-
}
-
-
@SneakyThrows
-
@Override
-
protected UserDetails
retrieveUser
(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
-
//为计时攻击的防御做准备
-
prepareTimingAttackProtection();
-
-
//拿到当前请求 request
-
HttpServletRequest
request
= Optional.ofNullable(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest())
-
.orElseThrow((Supplier<Throwable>) () ->
new
InternalAuthenticationServiceException(
"web request is empty"));
-
//在 request 里面拿到 grant_type
-
Map<String, String> paramMap = ServletUtil.getParamMap(request);
-
String
grantType
= paramMap.get(OAuth2ParameterNames.GRANT_TYPE);
-
-
//从容器中获取到 UserDetailsService bean
-
Map<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil
-
.getBeansOfType(PigUserDetailsService.class);
-
-
Optional<PigUserDetailsService> optional = userDetailsServiceMap.values().stream()
-
//过滤掉不是当前登录模式的 service
-
.filter(service -> service.support(grantType))
-
.max(Comparator.comparingInt(Ordered::getOrder));
-
try {
-
-
UserDetails
loadedUser
= optional.get().loadUserByUsername(username);
-
if (loadedUser ==
null) {
-
throw
new
InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
-
}
else {
-
return loadedUser;
-
}
-
}
catch (UsernameNotFoundException var4) {
-
//缓解计时攻击
-
this.mitigateAgainstTimingAttack(authentication);
-
throw var4;
-
}
catch (InternalAuthenticationServiceException var5) {
-
throw var5;
-
}
catch (Exception var6) {
-
throw
new
InternalAuthenticationServiceException(var6.getMessage(), var6);
-
}
-
-
}
-
-
}
-
//6.在 pig-auth 模块里面继承一套基于 OAuth2ResourceOwnerBaseAuthenticationXXXX 的 converter、token、provider 类,完全按照密码模式登录的编写就好;
-
-
public
class
OAuth2ResourceOwnerSmsAuthenticationToken
extends
OAuth2ResourceOwnerBaseAuthenticationToken {
-
-
public
OAuth2ResourceOwnerSmsAuthenticationToken
(AuthorizationGrantType authorizationGrantType, Authentication clientPrincipal, Set<String> scopes, Map<String, Object> additionalParameters) {
-
super(authorizationGrantType, clientPrincipal, scopes, additionalParameters);
-
}
-
}
-
-
-
public
class
OAuth2ResourceOwnerSmsAuthenticationConverter
extends
OAuth2ResourceOwnerBaseAuthenticationConverter {
-
-
/**
-
* @Description: 是否支持此convert
-
* @param: * @param grantType
-
* @return: boolean
-
**/
-
@Override
-
public
boolean
support
(String grantType) {
-
return SecurityConstants.APP.equals(grantType);
-
}
-
-
@Override
-
public OAuth2ResourceOwnerBaseAuthenticationToken
buildToken
(Authentication clientPrincipal, Set requestedScopes, Map additionalParameters) {
-
return
new
OAuth2ResourceOwnerSmsAuthenticationToken(
new
AuthorizationGrantType(SecurityConstants.APP), clientPrincipal, requestedScopes, additionalParameters);
-
}
-
-
/**
-
* @Description: 校验扩展参数 密码模式密码必须不为空
-
* @param: * @param request
-
* @return: void
-
**/
-
@Override
-
public
void
checkParams
(HttpServletRequest request) {
-
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
-
//从请求中拿到 mobile 属性的值
-
String
phone
= parameters.getFirst(SecurityConstants.SMS_PARAMETER_NAME);
-
//防止有多个 mobile 属性
-
if (!StringUtils.hasText(phone) || parameters.get(SecurityConstants.SMS_PARAMETER_NAME).size() !=
1) {
-
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, SecurityConstants.SMS_PARAMETER_NAME,
-
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
-
}
-
}
-
-
}
-
-
-
@Slf4j
-
public
class
OAuth2ResourceOwnerSmsAuthenticationProvider
extends
OAuth2ResourceOwnerBaseAuthenticationProvider<OAuth2ResourceOwnerSmsAuthenticationToken> {
-
-
public
OAuth2ResourceOwnerSmsAuthenticationProvider
(AuthenticationManager authenticationManager, OAuth2AuthorizationService oAuth2AuthorizationService, OAuth2TokenGenerator<? extends OAuth2Token> oAuth2TokenGenerator) {
-
super(authenticationManager, oAuth2AuthorizationService, oAuth2TokenGenerator);
-
}
-
-
@Override
-
public
boolean
supports
(Class<?> authentication) {
-
boolean
supports
= OAuth2ResourceOwnerSmsAuthenticationToken.class.isAssignableFrom(authentication);
-
log.debug(
"supports authentication=" + authentication +
" returning " + supports);
-
return supports;
-
}
-
-
@Override
-
public
void
checkClient
(RegisteredClient registeredClient) {
-
assert registeredClient !=
null;
-
//检查当前登录的客户端是否支持此模式的登录
-
if (!registeredClient.getAuthorizationGrantTypes().contains(
new
AuthorizationGrantType(SecurityConstants.APP))) {
-
throw
new
OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
-
}
-
}
-
-
@Override
-
public UsernamePasswordAuthenticationToken
buildUserAuthenToken
(Map<String, Object> reqParameters) {
-
//从请求中拿到 mobile 属性的值
-
String
phone
= (String) reqParameters.get(SecurityConstants.SMS_PARAMETER_NAME);
-
//创建未认证的 token
-
return
new
UsernamePasswordAuthenticationToken(phone,
null);
-
}
-
}
-
//7.修改 AuthorizationServerConfiguration 类,将短信验证码模式的 converter 和 provider 添加到配置里面;
-
-
@EnableWebSecurity(debug = true)
//这个注解会触发创建 HttpSecurity bean ~
-
@RequiredArgsConstructor
-
public
class
AuthorizationServerConfiguration {
-
-
/**
-
* @Description: request -> xToken 注入请求转换器
-
* 1、授权码模式(暂无)
-
* 2、隐藏式(暂无)
-
* 3、密码式(自定义的)
-
* 4、客户端凭证(暂无)
-
* @param
-
* @Return: org.springframework.security.web.authentication.AuthenticationConverter
-
*/
-
public AuthenticationConverter
accessTokenRequestConverter
(){
-
//new一个token转换器委托器,其中包含自定义密码模式认证转换器和刷新令牌认证转换器
-
return
new
DelegatingAuthenticationConverter(Arrays.asList(
-
// ——自定义密码模式登录
-
new
OAuth2ResourceOwnerPasswordAuthenticationConverter(),
-
// ——自定义短信验证码模式登录
-
new
OAuth2ResourceOwnerSmsAuthenticationConverter(),
-
// 访问令牌请求用于OAuth 2.0刷新令牌授权 ——刷新token
-
new
OAuth2RefreshTokenAuthenticationConverter()
-
//有需要到就要添加上
-
// // 访问令牌请求用于OAuth 2.0授权码授权 ——授权码模式获取token
-
// new OAuth2AuthorizationCodeAuthenticationConverter(),
-
// // 授权请求(或同意)用于OAuth 2.0授权代码授权 ——授权码模式获取code
-
// new OAuth2AuthorizationCodeRequestAuthenticationConverter()
-
));
-
}
-
-
/**
-
* @Description: 注入所有自定义认证授权需要的 provider 对象
-
* 1. 密码模式 </br>
-
* 2. 短信登录 (暂无)</br>
-
* @param http
-
* @Return: void
-
*/
-
public
void
addCustomOAuth2GrantAuthenticationProvider
(HttpSecurity http){
-
//从shareObject中获取到授权管理业务类(主要负责管理已认证的授权信息)
-
OAuth2AuthorizationService
oAuth2AuthorizationService
= http.getSharedObject(OAuth2AuthorizationService.class);
-
//从shareObject中获取到认证管理类
-
AuthenticationManager
authenticationManager
= http.getSharedObject(AuthenticationManager.class);
-
-
//new一个自定义处理密码模式的授权提供方,其中重点需要注入token生成器
-
OAuth2ResourceOwnerPasswordAuthenticationProvider
oAuth2ResourceOwnerPasswordAuthenticationProvider
=
-
new
OAuth2ResourceOwnerPasswordAuthenticationProvider(authenticationManager, oAuth2AuthorizationService, oAuth2TokenGenerator());
-
//new一个自定义处理短信验证码模式的授权提供方,其中重点需要注入token生成器
-
OAuth2ResourceOwnerSmsAuthenticationProvider
oAuth2ResourceOwnerSmsAuthenticationProvider
=
-
new
OAuth2ResourceOwnerSmsAuthenticationProvider(authenticationManager, oAuth2AuthorizationService, oAuth2TokenGenerator());
-
-
-
// 将自定义处理密码模式的授权提供方添加到安全配置中
-
http.authenticationProvider(
new
PigDaoAuthenticationProvider());
-
// 将自定义用户认证提供方添加到安全配置中
-
http.authenticationProvider(oAuth2ResourceOwnerPasswordAuthenticationProvider);
-
// 将自定义用户认证提供方添加到安全配置中
-
http.authenticationProvider(oAuth2ResourceOwnerSmsAuthenticationProvider);
-
}
-
-
}
//8.在数据库中找到 sys_oauth_client_details 表,给使用的客户端账号 authorized_grant_types 里面加上短信验证登录的标识,只有有该登录模式的标识才能够使用该登录模式。
B3.测试
先测试获取短信验证码接口成功~
注意,记得修改右上角的环境!

在ApiFox里创建一个短信验证码登录的接口,使用有此登录模式的客户端账号,进行登录

转载:https://blog.csdn.net/vaevaevae233/article/details/127766304
