2024年Java最全一套解决Spring Security OAuth2授权模式扩展以及应用实战,特殊渠道拿到阿里大厂面试真题

最后

由于篇幅有限,这里就不一一罗列了,20道常见面试题(含答案)+21条MySQL性能调优经验小编已整理成Word文档或PDF文档

MySQL全家桶笔记

还有更多面试复习笔记分享如下

Java架构专题面试复习

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 匹配逻辑详见下面的两个方法

  • @see org.springframework.security.oauth2.provider.CompositeTokenGranter#grant(String, TokenRequest)

  • @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#grant(String, TokenRequest)

*/

private static final String GRANT_TYPE = “sms_code”;

private final AuthenticationManager authenticationManager;

public SmsCodeTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,

OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager

) {

super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);

this.authenticationManager = authenticationManager;

}

@Override

protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());

String mobile = parameters.get(“mobile”); // 手机号

String code = parameters.get(“code”); // 短信验证码

parameters.remove(“code”);

Authentication userAuth = new SmsCodeAuthenticationToken(mobile, code);

((AbstractAuthenticationToken) userAuth).setDetails(parameters);

try {

userAuth = this.authenticationManager.authenticate(userAuth);

} catch (AccountStatusException var8) {

throw new InvalidGrantException(var8.getMessage());

} catch (BadCredentialsException var9) {

throw new InvalidGrantException(var9.getMessage());

}

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: " + mobile);

}

}

}

SmsCodeAuthenticationProvider

=============================

复制代码123456789101112131415161718192021222324252627282930313233343536373839JAVA/**

  • 短信验证码认证授权提供者

  • @author xianrui

  • @date 2021/9/25

*/

@Data

public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

private UserDetailsService userDetailsService;

private MemberFeignClient memberFeignClient;

private StringRedisTemplate redisTemplate;

@Override

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;

String mobile = (String) authenticationToken.getPrincipal();

String code = (String) authenticationToken.getCredentials();

String codeKey = AuthConstants.SMS_CODE_PREFIX + mobile;

String correctCode = redisTemplate.opsForValue().get(codeKey);

// 验证码比对

if (StrUtil.isBlank(correctCode) || !code.equals(correctCode)) {

throw new BizException(“验证码不正确”);

} else {

redisTemplate.delete(codeKey);

}

UserDetails userDetails = ((MemberUserDetailsServiceImpl) userDetailsService).loadUserByMobile(mobile);

WechatAuthenticationToken result = new WechatAuthenticationToken(userDetails, new HashSet<>());

result.setDetails(authentication.getDetails());

return result;

}

@Override

public boolean supports(Class<?> authentication) {

return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);

}

}

AuthorizationServerConfig

=========================

在认证中心配置把 SmsCodeTokenGranter 添加到认证器的授权类型的集合中去。

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

2.2 阿里云免费短信申请

=============

访问

https://free.aliyun.com/product/cloudcommunication-free-trial?spm=5176.10695662.1128094.7.2a6b4bee30xtJx 申请阿里云免费短信试用

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

添加签名,等待审核通过

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

签名审核通过之后就可以创建 AccessKey 访问密钥

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

添加模板, 国内消息 → 模板管理 → 添加模板

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

签名审核通过后得到 AccessKey 和 模板审核通过得到模板CODE,接下来就可以进行项目整合了。

2.3 SpringBoot 整合阿里云 SMS 短信

===========================

SpringBoot 整合 SMS 网上教程很多,这里不画蛇添足,接下来简单说下 youlai-mall 整合阿里云 SMS 短信。完整源码

按惯例把短信封装成一个公共模块以便给其他需要短信的应用模块引用。

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

youlai-auth 引入 common-sms 依赖

复制代码123456XML

com.youlai

common-sms

其中 AliyunSmsProperties 需要的属性需要配置在 Nacos 的配置中心文件 youlai-auth.yaml

复制代码123456789YAML# 阿里云短信配置

aliyun:

sms:

accessKeyId: LTAI5tSxxxxxxNcD6diBJLyR

accessKeySecret: SoOWRqpjtSxxxxxxM8QZ2PZiMTJOVC

domain: dysmsapi.aliyuncs.com

regionId: cn-shanghai

templateCode: SMS_225xxx770

signName: 有来技术

发送短信验证码接口

复制代码12345678910111213141516JAVA@Api(tags = “短信验证码”)

@RestController

@RequestMapping(“/sms-code”)

@RequiredArgsConstructor

public class SmsCodeController {

private final AliyunSmsService aliyunSmsService;

@ApiOperation(value = “发送短信验证码”)

@ApiImplicitParam(name = “phoneNumber”, example = “17621590365”, value = “手机号”, required = true)

@PostMapping

public Result sendSmsCode(String phoneNumber) {

boolean result = aliyunSmsService.sendSmsCode(phoneNumber);

return Result.judge(result);

}

}

2.4 移动端接入短信验证码授权模式

==================

有来移动端 mall-app 使用 uni-app 跨平台应用的前端框架。因为一直以来有来商城都是以微信小程序的一个端呈现,所以 uni-app 的强大之处没法体现。借着这次给 mall-app 扩展手机短信验证码的授权模式的机会,为 H5、Android和IOS 添加手机短信验证码的登录界面。

先看下 mall-app 登录界面 在H5/Android/IOS 和 微信小程序的不同呈现效果。

H5/Android/IOS 登录界面

微信小程序 登录界面

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

登录页面 /pages/login/login.vue 在不同的平台有不同的呈现实现原理是通过 #ifdef MP 和 #ifndef MP 条件编译指令实现的,其中 #ifdef MP 是在小程序平台编译生效,而 #ifdef MP 是非小程序平台编译生效。

在开发编译时,当在 HBuilderX 工具栏点击运行选择不同的平台会有不同的页面呈现。

  1. 运行 → 运行到内置浏览器 → 手机短信验证码登录界面;

  2. 运行 → 运行到小程序模拟器 → 微信开发者工具 → 小程序授权登录界面;

说到接入 Spring Security OAuth2 扩展的手机短信验证码,重要的还是看如何传参。在 mall-app 的 /api/user.js 代码:

复制代码1234567891011121314151617JAVASCRIPT// H5/Android/IOS 手机短信验证码登录

// #ifndef MP

export function login( mobile,code) {

return request({

url: ‘/youlai-auth/oauth/token’,

method: ‘post’,

params: {

mobile: mobile,

code: code,

grant_type: ‘sms_code’

},

headers: {

‘Authorization’: ‘Basic bWFsbC1hcHA6MTIzNDU2’ // 客户端信息Base64加密,明文:mall-app:123456

}

})

}

// #endif

赋予mall-app 客户端支持 sms_code 模式

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

3. 测试

======

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

到此H5/Android/IOS移动端接入 Spring Security OAuth2 扩展的手机短信验证码授权模式已经完成。接下来扩展的授权模式是针对当下最火的微信小程序移动端的授权登录。

四. 微信授权模式

=========

1. 原理

======

微信小程序登录授权流程图如下,我们所扮演的角色是 开发者服务器,主要的工作是接收小程序端的 code 从微信服务器获取 openid 和 session_key 后在开发者服务器生成会话(token)返回给小程序,后续小程序携带token和开发者服务器进行交互,也就没有微信服务器啥事了。

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

Spring Security OAuth2 微信授权扩展和上面的手机短信验证码原理一样,添加授权者 WechatTokenGranter 构建 WechatAuthenticationToken , 匹配到认证提供者

WechatAuthenticationProvider ,在其 authenticate 方法完成认证授权逻辑。

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

2. 实战

======

2.1 微信授权模式扩展

============

WechatTokenGranter

==================

WechatTokenGranter 微信授权者接收 code 、encryptedData 、iv 构建 WechatAuthenticationToken

复制代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253JAVA/**

  • 微信授权者

  • @author xianrui

  • @date 2021/9/25

*/

public class WechatTokenGranter extends AbstractTokenGranter {

/**

  • 声明授权者 CaptchaTokenGranter 支持授权模式 wechat

  • 根据接口传值 grant_type = wechat 的值匹配到此授权者

  • 匹配逻辑详见下面的两个方法

  • @see org.springframework.security.oauth2.provider.CompositeTokenGranter#grant(String, TokenRequest)

  • @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#grant(String, TokenRequest)

*/

private static final String GRANT_TYPE = “wechat”;

private final AuthenticationManager authenticationManager;

public WechatTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager) {

super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);

this.authenticationManager = authenticationManager;

}

@Override

protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());

String code = parameters.get(“code”);

String encryptedData = parameters.get(“encryptedData”);

String iv = parameters.get(“iv”);

parameters.remove(“code”);

parameters.remove(“encryptedData”);

parameters.remove(“iv”);

Authentication userAuth = new WechatAuthenticationToken(code, encryptedData,iv); // 未认证状态

((AbstractAuthenticationToken) userAuth).setDetails(parameters);

try {

userAuth = this.authenticationManager.authenticate(userAuth); // 认证中

} catch (Exception e) {

throw new InvalidGrantException(e.getMessage());

}

if (userAuth != null && userAuth.isAuthenticated()) { // 认证成功

OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);

return new OAuth2Authentication(storedOAuth2Request, userAuth);

} else { // 认证失败

throw new InvalidGrantException("Could not authenticate code: " + code);

}

}

}

WechatAuthenticationProvider

============================

最终在微信认证提供者的 authenticate() 方法里完成认证逻辑,成功返回token。

复制代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960JAVA/**

  • 微信认证提供者

  • @author xianrui

  • @date 2021/9/25

*/

@Data

public class WechatAuthenticationProvider implements AuthenticationProvider {

private UserDetailsService userDetailsService;

private WxMaService wxMaService;

private MemberFeignClient memberFeignClient;

/**

  • 微信认证

  • @param authentication

  • @return

  • @throws AuthenticationException

*/

@Override

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

WechatAuthenticationToken authenticationToken = (WechatAuthenticationToken) authentication;

String code = (String) authenticationToken.getPrincipal();

WxMaJscode2SessionResult sessionInfo = null;

try {

sessionInfo = wxMaService.getUserService().getSessionInfo(code);

} catch (WxErrorException e) {

e.printStackTrace();

}

String openid = sessionInfo.getOpenid();

Result memberAuthResult = memberFeignClient.loadUserByOpenId(openid);

// 微信用户不存在,注册成为新会员

if (memberAuthResult != null && ResultCode.USER_NOT_EXIST.getCode().equals(memberAuthResult.getCode())) {

String sessionKey = sessionInfo.getSessionKey();

String encryptedData = authenticationToken.getEncryptedData();

String iv = authenticationToken.getIv();

// 解密 encryptedData 获取用户信息

WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(sessionKey, encryptedData, iv);

UmsMember member = new UmsMember();

BeanUtil.copyProperties(userInfo, member);

member.setOpenid(openid);

member.setStatus(GlobalConstants.STATUS_YES);

memberFeignClient.add(member);

}

UserDetails userDetails = ((MemberUserDetailsServiceImpl) userDetailsService).loadUserByOpenId(openid);

WechatAuthenticationToken result = new WechatAuthenticationToken(userDetails, new HashSet<>());

result.setDetails(authentication.getDetails());

return result;

}

@Override

public boolean supports(Class<?> authentication) {

return WechatAuthenticationToken.class.isAssignableFrom(authentication);

}

}

2.2 微信小程序接入微信授权模式

=================

同样是在 mall-app 的接口文件中 /api/user.js,先让我们看下小程序端如何传值?

复制代码123456789101112131415161718JAVASCRIPT// 小程序授权登录

// #ifdef MP

export function login(code, encryptedData,iv) {

return request({

url: ‘/youlai-auth/oauth/token’,

method: ‘post’,

params: {

code: code,

encryptedData: encryptedData,

iv:iv,

grant_type: ‘wechat’

},

headers: {

‘Authorization’: ‘Basic bWFsbC13ZWFwcDoxMjM0NTY=’ // 客户端信息Base64加密,明文:mall-weapp:123456

}

})

}

// #endif

设置 OAuth2 客户端支持 wechat 授权模式

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

3. 测试

======

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

到此微信授权扩展完成,实际业务场景常用的3种授权模式也就告一段落。

但是如果你对 Spring Security OAuth2 有些了解的话,你会有疑问这些扩展的模式对应的刷新模式需不需要做什么调整呢?

如果扩展只是针对一种用户体系以及一种认证方式(用户名/手机号/openid)的话,比如验证码 模式的扩展,就不需要对刷新模式做调整。

但是如果是多用户体系或者多种认证方式,youlai-mall 就是多用户体系以及多种认证方式,这时你必须做些调整来适配,不过改动不大,具体为什么调整和如何调整下文细说。

五. 多用户体系刷新模式

============

1. 原理

======

刷新模式 时序图如下,相较于密码模式还只是 Granter 和 Provider的变动。

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

着重说一下刷新模式的认证提供者

PreAuthenticatedAuthenticationProvider ,其 authenticate() 认证方法只做用户状态校验,check() 方法调用

AccountStatusUserDetailsChecker#check(UserDetails)。

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

注意 下

this.preAuthenticatedUserDetailsService.loadUserDetails((PreAuthenticatedAuthenticationToken)authentication); 的 preAuthenticatedUserDetailsService 用户服务。

在没有进行授权模式扩展的时候,是下面这样设置的

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

然后在

AuthorizationServerEndpointsConfigurer#addUserDetailsService(DefaultTokenServices,UserDetailsService) 构造 PreAuthenticatedAuthenticationProvider 里设置了 UserDetailService用户服务。

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

这样在多用户体系认证下问题可想而知,用户分别有系统用户和会员用户,这里固定成一个用户服务肯定是行不通的,扩展授权模式创建 Provider 时可以指定具体的用户服务 UserDetailService,就如下面这样:

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

你可以为每个授权模式扩展新增对应的刷新模式,但是这样的话比较麻烦,本文的实现方案核心图的是简单有效,所以这里使用的另一种方案,重新设置

PreAuthenticatedAuthenticationProvider 的 preAuthenticatedUserDetailsService 属性,让其有判断选择用户体系和认证方式的能力。

2. 实战

======

首先我们清楚一个 OAuth2 客户端基本对应的是一个用户体系,比如 youlai-mall 项目的客户端和用户体系对应关系如下表:

OAuth2 客户端名称

OAuth2 客户端ID

用户体系

管理系统

mall-admin-web

系统用户

H5/Android/IOS 移动端

mall-app

商城会员

小程序端

mall-weapp

商城会员

那就有一个很简单有效的思路,可以在系统内部维护一个如上表的映射关系 Map,然后根据传递的客户端ID去选择用户体系。

就这?当然不是,还有个点你必须要考虑到,举个例子虽然移动端的用户体系是会员用户,但是它可能有多种认证方式呀,比如可以同时支持手机短信验证码和用户名密码甚至更多的认证方式。

而 Spring Security OAuth2 默认的 UserDetailsService 接口只有一个 loadUserByUsername() 方法,很显然是做不到会员体系支持多种认证方式的。

复制代码123JAVApublic interface UserDetailsService {

UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;

}

所以需要在 UserDetailsService 的实现类新增认证方式,然后在运行时将 UserDetailsService 转为具体的实现类,具体可看下有来项目的

MemberUserDetailsServiceImpl 的实现,同时支持手机号和三方标识 openid 获取用户认证信息,即两种不同的认证方式。

复制代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273JAVA/**

  • 商城会员用户认证服务

  • @author xianrui

*/

@Service(“memberUserDetailsService”)

@RequiredArgsConstructor

public class MemberUserDetailsServiceImpl implements UserDetailsService {

private final MemberFeignClient memberFeignClient;

@Override

public UserDetails loadUserByUsername(String username) {

return null;

}

/**

  • 手机号码认证方式

  • @param mobile

  • @return

*/

public UserDetails loadUserByMobile(String mobile) {

MemberUserDetails userDetails = null;

Result result = memberFeignClient.loadUserByMobile(mobile);

if (Result.isSuccess(result)) {

MemberAuthDTO member = result.getData();

if (null != member) {

userDetails = new MemberUserDetails(member);

userDetails.setAuthenticationMethod(AuthenticationMethodEnum.MOBILE.getValue()); // 认证方式:OpenId

}

}

if (userDetails == null) {

throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg());

} else if (!userDetails.isEnabled()) {

throw new DisabledException(“该账户已被禁用!”);

} else if (!userDetails.isAccountNonLocked()) {

throw new LockedException(“该账号已被锁定!”);

} else if (!userDetails.isAccountNonExpired()) {

throw new AccountExpiredException(“该账号已过期!”);

}

return userDetails;

}

/**

  • openid 认证方式

  • @param openId

  • @return

*/

public UserDetails loadUserByOpenId(String openId) {

MemberUserDetails userDetails = null;

Result result = memberFeignClient.loadUserByOpenId(openId);

if (Result.isSuccess(result)) {

MemberAuthDTO member = result.getData();

if (null != member) {

userDetails = new MemberUserDetails(member);

userDetails.setAuthenticationMethod(AuthenticationMethodEnum.OPENID.getValue()); // 认证方式:OpenId

}

}

if (userDetails == null) {

throw new UsernameNotFoundException(ResultCode.USER_NOT_EXIST.getMsg());

} else if (!userDetails.isEnabled()) {

throw new DisabledException(“该账户已被禁用!”);

} else if (!userDetails.isAccountNonLocked()) {

throw new LockedException(“该账号已被锁定!”);

} else if (!userDetails.isAccountNonExpired()) {

throw new AccountExpiredException(“该账号已过期!”);

}

return userDetails;

}

}

新增的

PreAuthenticatedUserDetailsService 可根据客户端和认证方式选择UserDetailService 和方法获取用户信息 UserDetail

复制代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768JAVA/**

  • 刷新token再次认证 UserDetailsService

  • @author xianrui

  • @date 2021/10/2

*/

@NoArgsConstructor

public class PreAuthenticatedUserDetailsService implements AuthenticationUserDetailsService, InitializingBean {

/**

  • 客户端ID和用户服务 UserDetailService 的映射

  • @see com.youlai.auth.security.config.AuthorizationServerConfig#tokenServices(AuthorizationServerEndpointsConfigurer)

*/

private Map<String, UserDetailsService> userDetailsServiceMap;

public PreAuthenticatedUserDetailsService(Map<String, UserDetailsService> userDetailsServiceMap) {

Assert.notNull(userDetailsServiceMap, “userDetailsService cannot be null.”);

this.userDetailsServiceMap = userDetailsServiceMap;

}

@Override

public void afterPropertiesSet() throws Exception {

Assert.notNull(this.userDetailsServiceMap, “UserDetailsService must be set”);

}

/**

  • 重写PreAuthenticatedAuthenticationProvider 的 preAuthenticatedUserDetailsService 属性,可根据客户端和认证方式选择用户服务 UserDetailService 获取用户信息 UserDetail

  • @param authentication

  • @return

  • @throws UsernameNotFoundException

*/

@Override

public UserDetails loadUserDetails(T authentication) throws UsernameNotFoundException {

String clientId = RequestUtils.getOAuth2ClientId();

// 获取认证方式,默认是用户名 username

AuthenticationMethodEnum authenticationMethodEnum = AuthenticationMethodEnum.getByValue(RequestUtils.getAuthenticationMethod());

UserDetailsService userDetailsService = userDetailsServiceMap.get(clientId);

if (clientId.equals(SecurityConstants.APP_CLIENT_ID)) {

// 移动端的用户体系是会员,认证方式是通过手机号 mobile 认证

MemberUserDetailsServiceImpl memberUserDetailsService = (MemberUserDetailsServiceImpl) userDetailsService;

switch (authenticationMethodEnum) {

case MOBILE:

return memberUserDetailsService.loadUserByMobile(authentication.getName());

default:

return memberUserDetailsService.loadUserByUsername(authentication.getName());

}

} else if (clientId.equals(SecurityConstants.WEAPP_CLIENT_ID)) {

// 小程序的用户体系是会员,认证方式是通过微信三方标识 openid 认证

MemberUserDetailsServiceImpl memberUserDetailsService = (MemberUserDetailsServiceImpl) userDetailsService;

switch (authenticationMethodEnum) {

case OPENID:

return memberUserDetailsService.loadUserByOpenId(authentication.getName());

default:

return memberUserDetailsService.loadUserByUsername(authentication.getName());

}

} else if (clientId.equals(SecurityConstants.ADMIN_CLIENT_ID)) {

// 管理系统的用户体系是系统用户,认证方式通过用户名 username 认证

switch (authenticationMethodEnum) {

default:

return userDetailsService.loadUserByUsername(authentication.getName());

}

} else {

return userDetailsService.loadUserByUsername(authentication.getName());

}

}

}

AuthorizationServerConfig 配置重新设置

PreAuthenticatedAuthenticationProvider 的 preAuthenticatedUserDetailsService 属性值

复制代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071JAVA /**

  • 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)

*/

@Override

public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

// Token增强

TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();

List tokenEnhancers = new ArrayList<>();

tokenEnhancers.add(tokenEnhancer());

tokenEnhancers.add(jwtAccessTokenConverter());

tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

// 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者

List granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));

// 添加验证码授权模式授权者

granterList.add(new CaptchaTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),

endpoints.getOAuth2RequestFactory(), authenticationManager, stringRedisTemplate

));

// 添加手机短信验证码授权模式的授权者

granterList.add(new SmsCodeTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),

endpoints.getOAuth2RequestFactory(), authenticationManager

));

// 添加微信授权模式的授权者

granterList.add(new WechatTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),

endpoints.getOAuth2RequestFactory(), authenticationManager

));

CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);

endpoints

.authenticationManager(authenticationManager)

.accessTokenConverter(jwtAccessTokenConverter())

.tokenEnhancer(tokenEnhancerChain)

.tokenGranter(compositeTokenGranter)

/** refresh token有两种使用方式:重复使用(true)、非重复使用(false),默认为true

  • 1 重复使用:access token过期刷新时, refresh token过期时间未改变,仍以初次生成的时间为准

  • 2 非重复使用:access token过期刷新时, refresh token过期时间延续,在refresh token有效期内刷新便永不失效达到无需再次登录的目的

*/

.reuseRefreshTokens(true)

.tokenServices(tokenServices(endpoints))

;

}

public DefaultTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {

TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();

List tokenEnhancers = new ArrayList<>();

tokenEnhancers.add(tokenEnhancer());

tokenEnhancers.add(jwtAccessTokenConverter());

tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

DefaultTokenServices tokenServices = new DefaultTokenServices();

tokenServices.setTokenStore(endpoints.getTokenStore());

tokenServices.setSupportRefreshToken(true);

tokenServices.setClientDetailsService(clientDetailsService);

tokenServices.setTokenEnhancer(tokenEnhancerChain);

// 多用户体系下,刷新token再次认证客户端ID和 UserDetailService 的映射Map

Map<String, UserDetailsService> clientUserDetailsServiceMap = new HashMap<>();

clientUserDetailsServiceMap.put(SecurityConstants.ADMIN_CLIENT_ID, sysUserDetailsService); // 管理系统客户端

clientUserDetailsServiceMap.put(SecurityConstants.APP_CLIENT_ID, memberUserDetailsService); // Android/IOS/H5 移动客户端

clientUserDetailsServiceMap.put(SecurityConstants.WEAPP_CLIENT_ID, memberUserDetailsService); // 微信小程序客户端

// 重新设置PreAuthenticatedAuthenticationProvider#preAuthenticatedUserDetailsService 能够根据客户端ID和认证方式区分用户体系获取认证用户信息

PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();

provider.setPreAuthenticatedUserDetailsService(new PreAuthenticatedUserDetailsService<>(clientUserDetailsServiceMap));

tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));

return tokenServices;

}

核心代码基本都在上面,在完成以上的调整之后刷新模式就可以了,接下来对新扩展的授权模式对应的刷新模式进行逐一测试。

3. 测试

======

3.1 Postman 导入 cURL 操作说明

========================

下面所有的测试都会把 cURL 贴出来,至于为什么强调这个?原来以为我把用 Postman 测试 Spring Security OAuth2 获取 token 的完整请求截图放入项目说明文档 README.md 这样就不会再有人问登录接口 403 报错,但事实反馈确实自己挺失望,以致于后来再有这样的问题基本上选择沉默了,希望大家换位思考理解下。所以这次想到的方案是把接口信息以 cURL 的形式贴出来,然后直接导入 Postman 测试。

下面是有来项目获取 token 的 cURL

复制代码12SHELLcurl --location --request POST ‘http://localhost:9999/youlai-auth/oauth/token?username=admin&password=123456&grant_type=password’ \

–header ‘Authorization: Basic bWFsbC1hZG1pbi13ZWI6MTIzNDU2’

进入 Postman 选择 File → Import → Raw text 把上面的 cURL 导入

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

3.2 密码模式测试

我的面试宝典:一线互联网大厂Java核心面试题库

以下是我个人的一些做法,希望可以给各位提供一些帮助:

整理了很长一段时间,拿来复习面试刷题非常合适,其中包括了Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!

image

283页的Java进阶核心pdf文档

Java部分:Java基础,集合,并发,多线程,JVM,设计模式

数据结构算法:Java算法,数据结构

开源框架部分:Spring,MyBatis,MVC,netty,tomcat

分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等

微服务部分:SpringBoot,SpringCloud,Dubbo,Docker

image

还有源码相关的阅读学习

image

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

1 Postman 导入 cURL 操作说明

========================

下面所有的测试都会把 cURL 贴出来,至于为什么强调这个?原来以为我把用 Postman 测试 Spring Security OAuth2 获取 token 的完整请求截图放入项目说明文档 README.md 这样就不会再有人问登录接口 403 报错,但事实反馈确实自己挺失望,以致于后来再有这样的问题基本上选择沉默了,希望大家换位思考理解下。所以这次想到的方案是把接口信息以 cURL 的形式贴出来,然后直接导入 Postman 测试。

下面是有来项目获取 token 的 cURL

复制代码12SHELLcurl --location --request POST ‘http://localhost:9999/youlai-auth/oauth/token?username=admin&password=123456&grant_type=password’ \

–header ‘Authorization: Basic bWFsbC1hZG1pbi13ZWI6MTIzNDU2’

进入 Postman 选择 File → Import → Raw text 把上面的 cURL 导入

万字长文一套搞定Spring Security OAuth2授权模式扩展以及应用实战

3.2 密码模式测试

我的面试宝典:一线互联网大厂Java核心面试题库

以下是我个人的一些做法,希望可以给各位提供一些帮助:

整理了很长一段时间,拿来复习面试刷题非常合适,其中包括了Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!

[外链图片转存中…(img-X3pdgkT8-1714878062697)]

283页的Java进阶核心pdf文档

Java部分:Java基础,集合,并发,多线程,JVM,设计模式

数据结构算法:Java算法,数据结构

开源框架部分:Spring,MyBatis,MVC,netty,tomcat

分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等

微服务部分:SpringBoot,SpringCloud,Dubbo,Docker

[外链图片转存中…(img-pFx8vl9O-1714878062698)]

还有源码相关的阅读学习

[外链图片转存中…(img-XrGW3XMj-1714878062698)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值