热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【JAVA】微服务中使用SpringSecurityOAuth2集成短信验证码登录和自定义登录(2)

hey-girl东拼西凑原创文章,若有歧义可留言,若需转载需标明出处。hey有话说:之前的文章中已经详细的介绍了登录认证流程。建议搞不清的兄弟可以先去瞄瞄。在往下看。项目中集成au



hey-girl东拼西凑原创文章,若有歧义可留言,若需转载需标明出处。

hey有话说: 之前的文章中已经详细的介绍了登录认证流程。建议搞不清的兄弟可以先去瞄瞄。在往下看。项目中集成auth2,给了我们内置的4种授权模式+一个令牌刷新的模式,用grant_type区分。
但是,仔细琢磨。这4种肯定是不能适合各种美好的需求的。不得整个洋气的手机登录或者验证码登录或者第三方微信啥的。


采用自定义grant_type来增加授权模式

注意:如果只是看实现,会很懵的。还是了解下内置的授权模式流程,在看自定义的。我们的小脑袋瓜才能收获满满。了解原理请看我之前的文章。

还有这是在原有的4种模式上新增,而不是说新增这一个,覆盖原有的。要是是并存

废话说完,开始整活。


  1. 大体思路分析。
    内置的5种模式TokenGranter 实现类,是不是都在CompositeTokenGranter这个类里面通过tokenGranters 管理在一起了。然后循环遍历对比grant_type,找到对应的实现类来处理授权
    而每种授权的方式对应一种AuthenticationProvider 实现类来实现。所有 AuthenticationProvider 实现类都通过 ProviderManager 中的 providers 集合存起来
    ,TokenGranter的实现类会整一个AuthenticationToken实现类给Manager(认证管理类),认证管理类里面也管理了很多Providers(认证具体实现类),那不又得遍历下,通过supports方法 ,看看AuthenticationToken 和谁匹配 。找到具体的那个AuthenticationProvider 去干活。
    大致的形容了下,是不是就知道我们自定义的时候要干啥了。

  2. 我们从头来,一步步捋清楚。做一个手机号+密码登录的模式练习下
    首先用老演员postman发起一个post请求。client_id和client_secret可以放参数中或者头部,随意就好了。
    手机号+密码
    动动我们小脑瓜想想。我们的授权模式grant_type:phone。这个模式是我们自己定义的,CompositeTokenGranter里面是没有管理的。我们想要程序认识新的模式。是不是得给安排个实现类。交给CompositeTokenGranter管理起来。

那有小可爱就犯糊涂了。我们怎么写实现类了。
来来来,看看下其他模式是怎么搞的,我们不能超越,但是可以先模仿是不是。
AbstractTokenGranter
那我们就挑个常用的,照着ResourceOwnerPasswordTokenGranter写。


  1. 创建一个PhoneCustomTokenGranter 继承 AbstractTokenGranter
    属性GRANT_TYPE 等于 phone

package com.hey.girl.auth.grant;
/**
* @Description: 自定义grant_type颁发令牌
* @author: heihei
* @date: 2021年12月03日 11:39
*/
public class PhoneCustomTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "phone";
private final AuthenticationManager authenticationManager;
public PhoneCustomTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory
) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, "phone");
}
protected PhoneCustomTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory,
String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticatiOnManager= authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
String phOne= (String)parameters.get("phone");
String password = (String)parameters.get("password");
parameters.remove("password");
Authentication userAuth = new PhoneAuthenticationToken(phone, password);
((AbstractAuthenticationToken)userAuth).setDetails(parameters);
try{
userAuth = authenticationManager.authenticate(userAuth);
}catch (Exception e){
throw new InvalidGrantException("Could not authenticate mobile: " + phone);
}
if (userAuth != null && userAuth.isAuthenticated()) {
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else {
throw new InvalidGrantException("Could not authenticate user: " + phone);
}
}
}

Q1- 代码解析kankan
A1- GRANT_TYPE:授权模式;authenticationManager 认证管理类

Q2- getOAuth2Authentication方法解析
A2- 这个方法是在生成令牌的时候被调用的。 OAuth2Request(等于TokenRequests+ClientDetails的整合)和Authorization(当前授权用户)合在一起生成一个OAuth2Authentication。
在这方法中,主要就是获取请求参数。手机号和密码登录。然后生成一个PhoneAuthenticationToken,传递给manager管理类,通过provider的supports方法。根据不同类型的token,去找对应的Provider做具体认证。认证完以后拿到Authentication。就可以创建OAuth2Authentication对象了。


  1. 在上面的逻辑中需要生成PhoneAuthenticationToken。所以是不是又可以照着我们常用的UsernamePasswordAuthenticationToken抄袭下了。

public class PhoneAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersiOnUID= 1L;
/**
* 身份
*/
private final Object principal;
/**
* 凭证
*/
private Object credentials;
public PhoneAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public PhoneAuthenticationToken( Object principal,Object credentials,Collection authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public void setAuthenticated(boolean authenticated) throws IllegalArgumentException{
Assert.isTrue(!authenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
}

  1. 上面的逻辑还得要个Provider,所以照着DaoAuthenticationProvider整一个
    PhoneAuthenticationProvider

/**
1. @Description: 认证具体实现类
2. @author: heihei
3. @date: 2021年12月03日 10:34
*/
@Setter
public class PhoneAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
/**
* 认证具体方法
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 断言所提供的对象是所提供类的实例
Assert.isInstanceOf(PhoneAuthenticationToken.class,authentication,()->{
return "Only PhoneAuthenticationToken is supported";
});
PhoneAuthenticationToken authenticatiOnToken= (PhoneAuthenticationToken) authentication;
String mobile = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
UserDetails user =userDetailsService.loadUserByUsername(mobile);
PhoneAuthenticationToken authenticatiOnResult= new PhoneAuthenticationToken(user, password,user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
/**
* support方法来表示自己支持那种Token的认证
*/
@Override
public boolean supports(Class aClass) {
// 也就是判断当前的Class对象所表示的类,是不是参数中传递的Class对象所表示的类的父类,超接口,
// 或者是相同的类型。是则返回true,否则返回false。
return PhoneAuthenticationToken.class.isAssignableFrom(aClass);
}
}

写到这里,是不是还有个疑问,UserDetailsService是不是得写个。之前写了个public class GirlUserDetailsServiceImpl implements UserDetailsService。在这个类里loadUserByUsername方法是去调取远程服务查询数据库等操作。但是我这里变了。我的需求可能是通过手机号或者邮箱等字段去查询用户。那么我就写个GirlPhoneUserDetailsService 具体业务逻辑,可根据实际改。主要是明白这里的作用

@Slf4j
@Service()
@RequiredArgsConstructor
public class GirlPhoneUserDetailsService implements UserDetailsService {
/**
* feign调用远程服务
*/
private final RemoteUserService remoteUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
R result = remoteUserService.info(username, SecurityConstants.FROM_IN);
UserDetails userDetails = getUserDetails(result);
return userDetails;
}
/**
* 构建user details
* @param result 用户信息
* @return
*/
private UserDetails getUserDetails(R result) {
if(result == null || result.getData() == null) {
throw new UsernameNotFoundException("用户不存在");
}
UserInfo userInfo = result.getData();
Set dbAuthsSet = new HashSet<>();
/* 获取角色
* ArrayUtil是糊涂的工具
*/
if (ArrayUtil.isNotEmpty(userInfo.getRoles())) {
// 获取角色
Arrays.stream(userInfo.getRoles()).forEach(role -> dbAuthsSet.add(SecurityConstants.ROLE + role));
// 获取资源
dbAuthsSet.addAll(Arrays.asList(userInfo.getPermissions()));
}
// 注意toArray(new Object[0])与toArray()在函数上是相同的
Collection authorities = AuthorityUtils
.createAuthorityList(dbAuthsSet.toArray(new String[0]));
SysUser user = userInfo.getSysUser();
// 构造security用户
return new GirlUser(user.getUserId(), user.getDeptId(), user.getUsername(),
SecurityConstants.BCRYPT + user.getPassword(),
StrUtil.equals(user.getLockFlag(), CommonConstants.STATUS_NORMAL),
true,true, true, authorities );
}
}

  1. 基本改写的写完了,我们是不是就可以跑起来了。No!!! 小脑袋瓜想想。这些新增的我们不得配置配置么。
  2. 配置PhoneCustomTokenGranter,你说你新增了模式。如何被管理。我们先看看程序本身的几种授权模式怎么被加载的
    目光聚焦到AuthorizationServerEndpointsConfigurer这里。调用了getDefaultTokenGranters()方法,并且创建了 CompositeTokenGranter的实例对象,进行初始化。程序默认写死了这些模式。
    AuthorizationServerEndpointsConfigurer
    getDefaultTokenGranters方法
    话说这份上,灵感是不是一下来了。我把我的新模式往后加,不就完事了么。所以只要要在AuthorizationServerConfig里面配置下。

// 贴部分重要代码
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancer())
.userDetailsService(userDetailsService)
.authenticationManager(authenticationManager)
.reuseRefreshTokens(false)
.pathMapping("/oauth/confirm_access", "/token/confirm_access")
.exceptionTranslator(new GirlWebResponseExceptionTranslator());
List tokenGranters = getDefaultTokenGranters(
endpoints.getTokenServices(),
endpoints.getClientDetailsService(),
endpoints.getAuthorizationCodeServices(),
endpoints.getOAuth2RequestFactory()
);
endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));
}
private List getDefaultTokenGranters(
AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetails,
AuthorizationCodeServices authorizationCodeServices,
OAuth2RequestFactory requestFactory
) {
List tokenGranters = new ArrayList();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails, requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
tokenGranters.add(implicit);
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
if (this.authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(this.authenticationManager, tokenServices, clientDetails, requestFactory));
}
tokenGranters.add(new PhoneCustomTokenGranter(
authenticationManager,tokenServices,
clientDetails,
requestFactory
));
return tokenGranters;
}

这这过程中遇到的问题。也是很常见的一种问题,就是我自己新增的模式覆盖程序提供的5种模式。可以看看这位作者写的添加自定义授权模式遇到的问题,和这位作者一样,我也想着用CompositeTokenGranter里的add方法,但是感觉不太知道哪里用比较合适。


  1. 是不是还感觉差点啥。是得没错。你新增的provider。你是不是得管理起来。
    这个配置就写WebSecurityConfigurerAdapter配置类中就好

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(phoneAuthenticationProvider());
auth.authenticationProvider(daoAuthenticationProvider());
}
@Bean
public PhoneAuthenticationProvider phoneAuthenticationProvider() {
PhoneAuthenticationProvider phOneAuthenticationProvider= new PhoneAuthenticationProvider();
phoneAuthenticationProvider.setUserDetailsService(userPhoneDetailsService);
phoneAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return phoneAuthenticationProvider;
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticatiOnProvider= new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}

这里也是auth.authenticationProvider必须加你要用的provider,不然程序之前有的就GG被覆盖了。

最后一步,你新增一个授权模式,你的数据库表得加对应的。不然会匹配不上的。在这里插入图片描述
测试:
在这里插入图片描述
总结:不知道有没有其他更好的方法,如果有欢迎留言。
还是那句话。先了解登录的一些流程。才能得心应手的扩展



推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • (三)多表代码生成的实现方法
    本文介绍了一种实现多表代码生成的方法,使用了java代码和org.jeecg框架中的相关类和接口。通过设置主表配置,可以生成父子表的数据模型。 ... [详细]
  • 移动端常用单位——rem的使用方法和注意事项
    本文介绍了移动端常用的单位rem的使用方法和注意事项,包括px、%、em、vw、vh等其他常用单位的比较。同时还介绍了如何通过JS获取视口宽度并动态调整rem的值,以适应不同设备的屏幕大小。此外,还提到了rem目前在移动端的主流地位。 ... [详细]
author-avatar
辽河儿女
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有