认证服务器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
/**
* 授权服务器配置 添加 BasicAuthenticationFilter ,主要针对 Basic XXX 请求头拦截 暴露oauth2端点暴露
*
* @description EnableAuthorizationServer 启用授权服务
* @description extends AuthorizationServerConfigurerAdapter
*/
@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
/**
* authorization_code:
* 请求示例:http://localhost:17000/oauth/authorize?client_id=admin&redirect_uri=http://localhost:8080/test&response_type=code
* redirect_uri,必须在redirectUris白名单集合中
* response_type = code
* autoApprove,false情况下,无法转发到 “/oauth/confirm_access” 端点
* {@link org.springframework.security.oauth2.provider.endpoint.WhitelabelApprovalEndpoint}
* code会通过redirect_uri 回传
* <p>
* password:
* 请求示例:[post]http://localhost:17000/oauth/token?grant_type=password&username=test&password=test1
* header需要包含 Authorization:Basic YWRtaW46YWRtaW4x(clientId:clientSecret拼接后base64)
* 需要认证管理器 {@link com.xrj.cloud.authorize.security.WebSecurityConfig} authenticationManagerBean()和daoAuthenticationProvider()
* <p>
* client_credentials:
* 请求示例:[post]http://localhost:17000/oauth/token?grant_type=client_credentials
* header需要包含 Authorization:Basic YWRtaW46YWRtaW4x(clientId:clientSecret拼接后base64)
* <p>
* implicit:
* 请求示例:http://localhost:17000/oauth/authorize?client_id=admin&redirect_uri=http://localhost:8080/test&response_type=token&state=121
* response_type = token
* state = 1432143
* 和授权码模式相似,response_type有变动,改为直接获取token,scope权限,state用于认证标记,传过去什么回调时传回来什么
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("admin")
.secret(passwordEncoder.encode("admin1"))
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(864000)
.redirectUris("http://localhost:8080/test") //转发回调白名单
.scopes("read", "user")
.autoApprove(false)
.autoApprove("user")
.authorizedGrantTypes("authorization_code", "implicit", "password", "refresh_token", "client_credentials");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.tokenKeyAccess("isAuthenticated()"); // 获取密钥需要身份认证,使用单点登录时必须配置
}
}
import com.xrj.cloud.authorize.service.UsernameUserDetailsService;
import com.xrj.cloud.common.config.security.provider.SimpleAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* 安全配置
*
* @ EnableWebSecurity 启用web安全配置
* @ EnableGlobalMethodSecurity 启用全局方法安全注解,就可以在方法上使用注解来对请求进行过滤
* @ EnableResourceService 开启 OAuth2AuthenticationProcessingFilter 过滤器
*/
@Configuration
@Order(10)
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UsernameUserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 需要配置这个支持password模式
* password grant type support.
*/
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public SimpleAuthenticationProvider daoAuthenticationProvider() {
SimpleAuthenticationProvider provider = new SimpleAuthenticationProvider();
// 设置userDetailsService
provider.setUserDetailsService(userDetailsService);
// 使用BCrypt进行密码的hash
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
}
常见问题
- 授权码模式下,无法转发授权页 ,应该是重写引起的。
//重写后,针对WebMvcConfigurationSupport类的自动装配失效
public class MvcConfig extends WebMvcConfigurationSupport {
//重写,增加解析器
@Override
protected void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(new InternalResourceViewResolver());
}
}
- Fastjson和Jackson不一致问题。在单点登录时,如果修改了默认转换器,应该会碰到的问题。
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import java.io.IOException;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
public class OAuth2HttpMessageConverter extends AbstractHttpMessageConverter<DefaultOAuth2AccessToken> {
private final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
public OAuth2HttpMessageConverter() {
super(APPLICATION_JSON_UTF8);
}
@Override
protected boolean supports(Class<?> clazz) {
return clazz.equals(DefaultOAuth2AccessToken.class);
}
@Override
protected DefaultOAuth2AccessToken readInternal(Class<? extends DefaultOAuth2AccessToken> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
throw new UnsupportedOperationException(
"This converter is only used for converting DefaultOAuth2AccessToken to json.");
}
@Override
protected void writeInternal(DefaultOAuth2AccessToken accessToken, HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
mappingJackson2HttpMessageConverter.write(accessToken, APPLICATION_JSON_UTF8, outputMessage);
}
}