记录下学习oauth时,如何自定义grant_type
创建一个新的Token模式,并继承AbstractAuthenticationToken,代码如下
/**
* 自定义Sms的AppAuthenticationToken
*
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = -2721661528106186767L;
private final Object principal;
public SmsCodeAuthenticationToken(String mobile) {
super(null);
this.principal = mobile;
setAuthenticated(false);
}
public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return this.principal;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
@SneakyThrows
public void setAuthenticated(boolean isAuthenticated) {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
2.创建一个新的Provider,并实现AuthenticationProvider
@AllArgsConstructor
public class SmsCodeProvider implements AuthenticationProvider {
private final UserDetailService userDetailService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;
/**
* 调用 {@link UserDetailsService}
*/
UserDetails user = userDetailService.loadUserByUserMobile((String) authenticationToken.getPrincipal());
if (Objects.isNull(user)) {
throw new InternalAuthenticationServiceException("手机号或验证码错误");
}
SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());
Object details = authenticationToken.getDetails();
authenticationResult.setDetails(details);
return authenticationResult;
}
@Override
public boolean supports(Class<?> aClass) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);
}
}
3.实现Provider,需要继承AbstractTokenGranter
/**
* 自定义验证码登录
*/
public class SmsCodeTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "sms";
private final AuthenticationManager authenticationManager;
private RedisUtil redisService;
private final String CODE_ = "lione.sms.code.";
protected SmsCodeTokenGranter(AuthenticationManager authenticationManager,AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
}
public SmsCodeTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory, RedisUtil redisService) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.redisService = redisService;
}
/**
* 实现方法
* @param client
* @param tokenRequest
* @return
*/
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
String mobile = parameters.get("mobile");
String code = parameters.get("code");
if (StringUtils.isBlank(code)) {
throw new InvalidGrantException("验证码为空");
}
// 校验key
Boolean aBoolean = redisService.hasKey(CODE_ + mobile);
if (!aBoolean) {
throw new InvalidGrantException("请发送验证码");
}
String s = redisService.get(CODE_ + mobile);
if (!s.equals(code)) {
throw new InvalidGrantException("验证码有误");
}
Authentication userAuth = new SmsCodeAuthenticationToken(mobile);
// TODO: 2023/8/31 赋值 关键
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = authenticationManager.authenticate(userAuth);
} catch (AccountStatusException | BadCredentialsException ase) {
//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
}
// If the username/password are wrong the spec says we should send 400/invalid grant
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate user: " + mobile);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
4.将创建的Provider注册到config中
4.1创建SecurityConfig并继承SecurityConfigurerAdapter
@Slf4j
@Component
public class SmsCodEAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private UserDetailService userDetailsService;
@Override
public void configure(HttpSecurity http) {
// 获取验证码提供者
SmsCodeProvider appAuthenticationProvider = new SmsCodeProvider(userDetailsService);
// 将短信验证码校验器注册到 HttpSecurity
http.authenticationProvider(appAuthenticationProvider);
}
}
4.2 并在WebSecurityConfigurerAdapter继承类中添加
@Resource
private SmsCodEAuthenticationSecurityConfig smsCodEAuthenticationSecurityConfig;
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config
= http.requestMatchers().anyRequest()
.and()
.formLogin()
.and()
.apply(noPasswordAuthenticationSecurityConfig)
.and()
.apply(smsCodEAuthenticationSecurityConfig).and()
.authorizeRequests();
config.antMatchers(SECURITY_ENDPOINTS).permitAll();
config
//任何请求
.anyRequest()
//都需要身份认证
.authenticated()
//csrf跨站请求
.and()
.csrf().disable();
}
6.最后再AuthorizationServerConfigurerAdapter实现类中添加方法
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
// 配置Token的存储方式
endpoints.exceptionTranslator(oAuthServerWebResponseExceptionTranslator());
// 注入WebSecurityConfig配置的bean
endpoints.authenticationManager(authenticationManager).tokenServices(tokenServices());
endpoints.tokenGranter(tokenGranter(endpoints));
}
/**
* 重点
* 先获取已经有的五种授权,然后添加我们自己的进去
*
* @param endpoints AuthorizationServerEndpointsConfigurer
* @return TokenGranter
*/
private TokenGranter tokenGranter(final AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));
granters.add(new NOPasswordTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory(), redisUtil));
granters.add(new SmsCodeTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory(), redisUtil));
return new CompositeTokenGranter(granters);
}
总结
创建一个新的登录类型时,需要分别实现AuthenticationProvider,AbstractTokenGranter,AbstractAuthenticationToken,SecurityConfigurerAdapter,最后需要再WebSecurityConfigurerAdapter的继承类中添加