本文介紹一種優雅的擴展AuthorizationServer認證方式的實現方法,可協助實現短信認證,微信openId認證等。
核心相關代碼:
@Configuration
@EnableAuthorizationServer
public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter
@Override
public void configure(
AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//配置AuthorizationServerEndpointsConfigurer眾多相關類,包括配置身份認證器,配置認證方式,TokenStore,TokenGranter,OAuth2RequestFactory
// @formatter:off
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService())
.accessTokenConverter(jwtAccessTokenConverter)
//設置自定義tokenGranter
.tokenGranter(customTokenGranter(endpoints))
.authorizationCodeServices(authorizationCodeServices())
.setClientDetailsService(jdbcClientDetailsService());
// @formatter:on
//擴展token返回結果
if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancerList = new ArrayList();
enhancerList.add(jwtTokenEnhancer);
enhancerList.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(enhancerList);
//jwt
endpoints.tokenEnhancer(tokenEnhancerChain).accessTokenConverter(jwtAccessTokenConverter);
}
}
請關注customTokenGranter這個方法,以下為自定義重寫
/**
* 自定義TokenGranter
*/
private TokenGranter customTokenGranter(
AuthorizationServerEndpointsConfigurer endpoints) {
TokenGranter tokenGranter = new TokenGranter() {
private CompositeTokenGranter delegate;
@Override
public OAuth2AccessToken grant(
String grantType,
TokenRequest tokenRequest) {
if (delegate == null) {
delegate = new CompositeTokenGranter(getDefaultTokenGranters(endpoints));
}
return delegate.grant(grantType, tokenRequest);
}
};
return tokenGranter;
}
/**
* 這是從spring 的代碼中 copy出來的, 默認的幾個TokenGranter, 還原封不動加進去.
* 主要目的是覆蓋原來的List<TokenGranter>,方便我們添加自定義的授權方式
*/
private List<TokenGranter> getDefaultTokenGranters(
AuthorizationServerEndpointsConfigurer endpoints) {
AuthorizationServerTokenServices tokenServices = endpoints.getDefaultAuthorizationServerTokenServices();
AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();
List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices,
endpoints.getClientDetailsService(), requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, endpoints.getClientDetailsService(), requestFactory));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, endpoints.getClientDetailsService(),
requestFactory);
tokenGranters.add(implicit);
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, endpoints.getClientDetailsService(),
requestFactory));
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
endpoints.getClientDetailsService(), requestFactory));
//追加自定義
tokenGranters.add(new OpenIdTokenGranter(authenticationManager, tokenServices,
endpoints.getClientDetailsService(), requestFactory));
}
return tokenGranters;
}
OpenIdTokenGranter為新追加的方式
public class OpenIdTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "openid";
private final AuthenticationManager authenticationManager;
public OpenIdTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
}
protected OpenIdTokenGranter(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<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
String openId = parameters.get("openId");
//UserDetails userData = smsUserDetailService.loadUserByMobile(mobile);
Authentication userAuth = new OpenIdAuthenticationToken(openId);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
//具體會使用 OpenIdAuthenticationProvider 的 authenticate 去實現
userAuth = authenticationManager.authenticate(userAuth);
}
catch (AccountStatusException ase) {
//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
}
catch (BadCredentialsException e) {
// If the username/password are wrong the spec says we should send 400/invalid grant
throw new InvalidGrantException(e.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate openId: " + openId);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 520L;
private final Object principal;
private String openId;
public OpenIdAuthenticationToken(String openId) {
super(null);
this.principal = openId;
this.openId = openId;
setAuthenticated(false);
}
public OpenIdAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true); // must use super, as we override
}
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
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();
}
@Override
public Object getCredentials() {
return null;
}
/**
* get openId
*
* @return openId
*/
public String getOpenId() {
return openId;
}
}
public class OpenIdAuthenticationProvider implements AuthenticationProvider {
private UserInfoService userInfoService;
@Override
public Authentication authenticate(
Authentication authentication) throws AuthenticationException {
//執行校驗
OpenIdAuthenticationToken openIdAuthenticationToken = (OpenIdAuthenticationToken) authentication;
userInfoService = SpringBootUtil.getBean(UserInfoService.class);
String openId = (String) openIdAuthenticationToken.getPrincipal();
UserDetails userDetails = userInfoService.loadUserByOpenId(openId);
if (userDetails == null) {
throw new BadCredentialsException("Invalid openId!");
}
return new OpenIdAuthenticationToken(userDetails, userDetails.getAuthorities());
}
/**
* 為了使ProviderManger知道OpenIdAuthenticationToken該使用那個provider處理
*
* @see AuthenticationProvider#supports(Class)
*/
@Override
public boolean supports(
Class<?> authentication) {
return OpenIdAuthenticationToken.class.isAssignableFrom(authentication);
}
}
/**
* 將OpenIdAuthenticationProvider注入,在SecurityConfig中使用http.apply去使用
**/
@Component
public class AuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Override
public void configure(
HttpSecurity http) {
OpenIdAuthenticationProvider openIdProvider = new OpenIdAuthenticationProvider();
http.authenticationProvider(openIdProvider);
}
}
SecurityConfig中,
@Autowired
private AuthenticationSecurityConfig authenticationSecurityConfig;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用CSRF攻擊防禦
http.csrf().disable();
// 設置跨域
http.cors();
String[] matchUris = {"/sso/**", "/login/**", "/logout", "/logout/**", "/oauth/authorize"};
http.requestMatchers()
.antMatchers(matchUris).and()
// 認證授權
.authorizeRequests()
.authenticated().and().exceptionHandling().accessDeniedPage("/login/denied").and().cors().and()
//注入自定義驗證provider
.apply(authenticationSecurityConfig);