热门标签 | 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被覆盖了。

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



推荐阅读
  • 优化后的标题:深入探讨网关安全:将微服务升级为OAuth2资源服务器的最佳实践
    本文深入探讨了如何将微服务升级为OAuth2资源服务器,以订单服务为例,详细介绍了在POM文件中添加 `spring-cloud-starter-oauth2` 依赖,并配置Spring Security以实现对微服务的保护。通过这一过程,不仅增强了系统的安全性,还提高了资源访问的可控性和灵活性。文章还讨论了最佳实践,包括如何配置OAuth2客户端和资源服务器,以及如何处理常见的安全问题和错误。 ... [详细]
  • 问题场景用Java进行web开发过程当中,当遇到很多很多个字段的实体时,最苦恼的莫过于编辑字段的查看和修改界面,发现2个页面存在很多重复信息,能不能写一遍?有没有轮子用都不如自己造。解决方式笔者根据自 ... [详细]
  • 秒建一个后台管理系统?用这5个开源免费的Java项目就够了
    秒建一个后台管理系统?用这5个开源免费的Java项目就够了 ... [详细]
  • 本文详细介绍了 `org.apache.tinkerpop.gremlin.structure.VertexProperty` 类中的 `key()` 方法,并提供了多个实际应用的代码示例。通过这些示例,读者可以更好地理解该方法在图数据库操作中的具体用途。 ... [详细]
  • 二维码的实现与应用
    本文介绍了二维码的基本概念、分类及其优缺点,并详细描述了如何使用Java编程语言结合第三方库(如ZXing和qrcode.jar)来实现二维码的生成与解析。 ... [详细]
  • 一、Advice执行顺序二、Advice在同一个Aspect中三、Advice在不同的Aspect中一、Advice执行顺序如果多个Advice和同一个JointPoint连接& ... [详细]
  • 本文介绍了实时流协议(RTSP)的基本概念、组成部分及其与RTCP的交互过程,详细解析了客户端请求格式、服务器响应格式、常用方法分类及协议流程,并提供了SDP格式的深入解析。 ... [详细]
  • 本文介绍了如何使用Python爬取妙笔阁小说网仙侠系列中所有小说的信息,并将其保存为TXT和CSV格式。主要内容包括如何构造请求头以避免被网站封禁,以及如何利用XPath解析HTML并提取所需信息。 ... [详细]
  • ### 优化后的摘要本学习指南旨在帮助读者全面掌握 Bootstrap 前端框架的核心知识点与实战技巧。内容涵盖基础入门、核心功能和高级应用。第一章通过一个简单的“Hello World”示例,介绍 Bootstrap 的基本用法和快速上手方法。第二章深入探讨 Bootstrap 与 JSP 集成的细节,揭示两者结合的优势和应用场景。第三章则进一步讲解 Bootstrap 的高级特性,如响应式设计和组件定制,为开发者提供全方位的技术支持。 ... [详细]
  • 清华大学出版社 | 杨丹:基于MATLAB机器视觉的黑色素瘤皮肤癌检测技术及源代码分析(第1689期)
    清华大学出版社 | 杨丹:基于MATLAB机器视觉的黑色素瘤皮肤癌检测技术及源代码分析(第1689期) ... [详细]
  • 本文最初发表在Thorben Janssen的Java EE博客上,每周都会分享最新的Java新闻和动态。 ... [详细]
  • 当前求职状况及恳请行业前辈给予宝贵建议与指导 ... [详细]
  • Juval Löwy主张,每个类都应被视为服务,这并非是为了让服务无处不在,而是因为微服务是经过深思熟虑后系统分解的自然结果。在他的设计和构建的系统中,这种理念有助于提高模块化、可维护性和扩展性。通过将每个类视为独立的服务,系统能够更好地应对复杂性,实现更灵活的部署和更高的性能。 ... [详细]
  • 回顾过去十多年的开发经历,我在技术能力、培训机会、国际视野以及大型企业的工作经验方面都有了显著的提升。特别是从最初的月薪8k到如今的38k,这一过程中,我深刻体会到系统化学习对提升架构能力的重要性。最初踏入职场时,面对众多未知,我主要依赖团队领导的指导,专注于编写代码、管理数据库和进行测试。随着经验的积累和技术的不断进步,我逐渐意识到,只有通过系统化的学习和实践,才能在技术领域取得更大的突破。 ... [详细]
  • 基于Dubbo与Zipkin的微服务调用链路监控解决方案
    本文提出了一种基于Dubbo与Zipkin的微服务调用链路监控解决方案。通过抽象配置层,支持HTTP和Kafka两种数据上报方式,实现了灵活且高效的调用链路追踪。该方案不仅提升了系统的可维护性和扩展性,还为故障排查提供了强大的支持。 ... [详细]
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社区 版权所有