spring-security-oauth2-authorization-server:0.2.2
spring-boot:2.5.6
通过源码,发现授权码模式、客户端模式都实现或继承下面几个类
1.OAuth2AuthorizationGrantAuthenticationToken
用于存放 密码模式(password)所需的各种信息,包括 username、password、scopes 等
2.AuthenticationConverter
用于从HttpServletRequest中提取 密码模式(password) 所需的各种信息,包括username、password、scopes等
3.AuthenticationProvider
自定义 密码模式(password) 的核心逻辑,其功能主要如下:
- 检验 密码模式(password) 所需信息的正确性,包括 username、password、scopes 等
- 检验通过后,生成并返回 Access token、Refresh token、ID token 等信息
OAuth2PasswordAuthenticationToken
继承OAuth2AuthorizationGrantAuthenticationToken
类/**
* 用于存放username与password
*/
public class OAuth2PasswordAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
private static final long serialVersionUID = -559176897708927684L;
private final String username;
private final String password;
public OAuth2PasswordAuthenticationToken(String username, String password, Authentication clientPrincipal, Map<String, Object> additionalParameters) {
super(AuthorizationGrantType.PASSWORD, clientPrincipal, additionalParameters);
this.username = username;
this.password = password;
}
public String getUsername() {
return this.username;
}
public String getPassword() {
return this.password;
}
}
OAuth2PasswordAuthenticationConverter
实现AuthenticationConverter
/**
* 从HttpServletRequest中提取username与password,传递给OAuth2PasswordAuthenticationToken
*/
public class OAuth2PasswordAuthenticationConverter implements AuthenticationConverter {
@Override
public Authentication convert(HttpServletRequest request) {
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) {
return null;
}
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
if (!StringUtils.hasText(username) ||
parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.USERNAME,"");
}
String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
Map<String, Object> additionalParameters = new HashMap<>();
parameters.forEach((key, value) -> {
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
!key.equals(OAuth2ParameterNames.USERNAME) &&
!key.equals(OAuth2ParameterNames.PASSWORD)) {
additionalParameters.put(key, value.get(0));
}
});
return new OAuth2PasswordAuthenticationToken(username,password,clientPrincipal,additionalParameters);
}
}
OAuth2PasswordAuthenticationProvider
实现AuthenticationProvider
/**
* 密码认证的核心逻辑
*/
public class OAuth2PasswordAuthenticationProvider implements AuthenticationProvider {
private static final StringKeyGenerator DEFAULT_REFRESH_TOKEN_GENERATOR =
new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
private final OAuth2AuthorizationService authorizationService;
private final JwtEncoder jwtEncoder;
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {};
private Supplier<String> refreshTokenGenerator = DEFAULT_REFRESH_TOKEN_GENERATOR::generateKey;
private ProviderSettings providerSettings;
public OAuth2PasswordAuthenticationProvider(OAuth2AuthorizationService authorizationService, JwtEncoder jwtEncoder){
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(jwtEncoder, "jwtEncoder cannot be null");
this.authorizationService = authorizationService;
this.jwtEncoder = jwtEncoder;
}
public void setJwtCustomizer(OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer) {
Assert.notNull(jwtCustomizer, "jwtCustomizer cannot be null");
this.jwtCustomizer = jwtCustomizer;
}
public void setRefreshTokenGenerator(Supplier<String> refreshTokenGenerator) {
Assert.notNull(refreshTokenGenerator, "refreshTokenGenerator cannot be null");
this.refreshTokenGenerator = refreshTokenGenerator;
}
@Autowired
protected void setProviderSettings(ProviderSettings providerSettings) {
this.providerSettings = providerSettings;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2PasswordAuthenticationToken passwordAuthentication =
(OAuth2PasswordAuthenticationToken) authentication;
OAuth2ClientAuthenticationToken clientPrincipal =
getAuthenticatedClientElseThrowInvalidClient(passwordAuthentication);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
// 校验账户
var username = passwordAuthentication.getUsername();
if (StrUtil.isBlank(username)){
throw new OAuth2AuthenticationException("账户不能为空");
}
// 校验密码
var password = passwordAuthentication.getPassword();
if (StrUtil.isBlank(password)){
throw new OAuth2AuthenticationException("密码不能为空");
}
// 查询账户信息
UserDetails userDetails = getUserDetails(username);
if (ObjectUtil.isEmpty(userDetails)) {
throw new OAuth2AuthenticationException("账户信息不存在,请联系管理员");
}
// 校验密码
if (!PasswordEncoderFactories.createDelegatingPasswordEncoder().encode(password).equals(userDetails.getPassword())) {
throw new OAuth2AuthenticationException("密码不正确");
}
// 构造认证信息
Authentication principal = new UsernamePasswordAuthenticationToken(username, userDetails.getPassword(), userDetails.getAuthorities());
//region 直接构造一个OAuth2Authorization对象,实际场景中,应该去数据库进行校验
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(principal.getName())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.attribute(Principal.class.getName(), principal)
.attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, new HashSet<>())
.build();
//endregion
String issuer = this.providerSettings != null ? this.providerSettings.getIssuer() : null;
Set<String> authorizedScopes = authorization.getAttribute(
OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME);
// 构造jwt token信息
JoseHeader.Builder headersBuilder = JwtUtils.headers();
headersBuilder.header("client-id", registeredClient.getClientId());
headersBuilder.header("authorization-grant-type", passwordAuthentication.getGrantType().getValue());
JwtClaimsSet.Builder claimsBuilder = JwtUtils.accessTokenClaims(registeredClient, issuer, authorization.getPrincipalName(), authorizedScopes);
// @formatter:off
JwtEncodingContext context = JwtEncodingContext.with(headersBuilder, claimsBuilder)
.registeredClient(registeredClient)
.principal(authorization.getAttribute(Principal.class.getName()))
.authorization(authorization)
.authorizedScopes(authorizedScopes)
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.authorizationGrant(passwordAuthentication)
.build();
// @formatter:on
this.jwtCustomizer.customize(context);
JoseHeader headers = context.getHeaders().build();
JwtClaimsSet claims = context.getClaims().build();
Jwt jwtAccessToken = this.jwtEncoder.encode(headers, claims);
// 生成token
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwtAccessToken.getTokenValue(), jwtAccessToken.getIssuedAt(),
jwtAccessToken.getExpiresAt(), authorizedScopes);
return new OAuth2AccessTokenAuthenticationToken(
registeredClient, clientPrincipal, accessToken);
}
@Override
public boolean supports(Class<?> authentication) {
return OAuth2PasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
private OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
OAuth2ClientAuthenticationToken clientPrincipal = null;
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
}
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
return clientPrincipal;
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
}
/**
* 获取并组装用户信息
*/
@SneakyThrows
private UserDetails getUserDetails(String username) {
return User.builder()
.username("用户名")
.password("密码")
.authorities("权限")
.build();
}
阅读源码发现,我们自定义的类不能被spring扫描到,能力和时间有限也没找到注入的接口,所以采取了比较原始方法(也是比较暴力的方法) 重写
1、重写OAuth2TokenEndpointConfigurer
类
方法
HttpSecurityBuilder
增加密码认证相关类
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
JwtEncoder jwtEncoder = org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2ConfigurerUtils.getJwtEncoder(builder);
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2ConfigurerUtils.getJwtCustomizer(builder);
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
new OAuth2AuthorizationCodeAuthenticationProvider(
org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2ConfigurerUtils.getAuthorizationService(builder),
jwtEncoder);
// 添加密码认证处理逻辑
OAuth2PasswordAuthenticationProvider passwordAuthenticationProvider = new OAuth2PasswordAuthenticationProvider(
org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2ConfigurerUtils.getAuthorizationService(builder),
jwtEncoder);
if (jwtCustomizer != null) {
authorizationCodeAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
passwordAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
}
authenticationProviders.add(authorizationCodeAuthenticationProvider);
authenticationProviders.add(passwordAuthenticationProvider);
OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =
new OAuth2RefreshTokenAuthenticationProvider(
org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2ConfigurerUtils.getAuthorizationService(builder),
jwtEncoder);
if (jwtCustomizer != null) {
refreshTokenAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
}
authenticationProviders.add(refreshTokenAuthenticationProvider);
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
new OAuth2ClientCredentialsAuthenticationProvider(
org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2ConfigurerUtils.getAuthorizationService(builder),
jwtEncoder);
if (jwtCustomizer != null) {
clientCredentialsAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
}
authenticationProviders.add(clientCredentialsAuthenticationProvider);
return authenticationProviders;
}
2、重写OAuth2TokenEndpointFilter
OAuth2TokenEndpointFilter
方法加载密码认证相关类
public OAuth2TokenEndpointFilter(AuthenticationManager authenticationManager, String tokenEndpointUri) {
this.accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
this.errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
this.authenticationDetailsSource = new WebAuthenticationDetailsSource();
this.authenticationSuccessHandler = this::sendAccessTokenResponse;
this.authenticationFailureHandler = this::sendErrorResponse;
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
Assert.hasText(tokenEndpointUri, "tokenEndpointUri cannot be empty");
this.authenticationManager = authenticationManager;
this.tokenEndpointMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name());
this.authenticationConverter = new DelegatingAuthenticationConverter(
Arrays.asList(
new OAuth2AuthorizationCodeAuthenticationConverter(),
new OAuth2RefreshTokenAuthenticationConverter(),
new OAuth2ClientCredentialsAuthenticationConverter(),
// 增加密码认证解析类
new OAuth2PasswordAuthenticationConverter(),
));
}
参照该方法可以实现openId登录、验证码登录等