折腾了一两周了, 问题始终没有解决, 只能来这里求助了, 希望知道的朋友帮忙下!
下面开始说需求:
公司有两个系统, 我们就称之为聊天系统和业务系统吧, 聊天系统是因为业务系统需要沟通才存在, 聊天系统本身是没有用户, 用户都是来自业务系统.
这两个系统都需要保存数据到数据库, 因为聊天系统需要保存聊天记录, 业务系统也有业务数据需要保存, 两个系统用的框架都是spring boot,而且都是使用 mybatis, 当然了, 出于安全的需要, 就用到了 spring security, 本来是想成立一个用户中心, 但是公司除了这两个系统外,还有其他系统, 这样牵连比较大, 所以没有成立用户中心, 但聊天系统和业务系统本身是需要数据交互的.
所以, 引入 spring security 了以后, 我就想到 SSO 单点登录, 网上了解了下, spring security 实现单点登录的方式还有好多种方式的, 比如用到 CAS, JWT 等等, 我就选择使用 @EnableOAuth2Sso 注解这个方式来实现单点登录, 但是我折腾了好久, 问题始终没有解决.
出现的问题是:
聊天系统(客户端)启动时, 会自动向业务系统(授权端)发起一个 get 请求: oauth/token_key,
业务系统也能收到这个 get 请求, 但它却重定向去获取 static/index.html, 这是业务系统的首页, 但由于前后端分离, 前端采用 vue 来开发, 所以不存在这个页面, 报 404 错误!
请注意: 这个 get 请求的地址在聊天系统(客户端)的 application.properties 文件中配置, key 为: security.oauth2.resource.jwt.key-uri
我产生的疑问是:
1) 聊天系统发起这个 get 请求的时候, 不应该去获取 index.html, 我也不知道发起这个 get 请求的目的是什么, 但他不应该去登录或者请求首页.
2) 网上看到文章, 聊天系统作为客户端, 使用 @EnableOAuth2Sso 来实现单点登录的时候, 聊天系统是不需要配置 security.oauth2.resource.jwt.key-uri 的, 但是我不配置就报错.报错如下:
Description:
Method springSecurityFilterChain in
org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration
required a bean of type 'org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateFactory' that could not be found.
The following candidates were found but could not be injected:
- Bean method 'userInfoRestTemplateFactory' in 'ResourceServerTokenServicesConfiguration' not loaded because @ConditionalOnMissingBean (types: org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration; SearchStrategy: all) found beans of type 'org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration' org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration
Action:
Consider revisiting the entries above or defining a bean of type 'org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateFactory' in your configuration.
Disconnected from the target VM, address: '127.0.0.1:9125', transport: 'socket'
Process finished with exit code 1
下面给出聊天系统(客户端) spring security 的安全配置:
@Configuration
//@EnableWebSecurity()
@EnableOAuth2Sso
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Resource
private DataSource dataSource;
@Bean
@Override protected UserDetailsService userDetailsService() {
JdbcUserDetailsManager manager = getUserDetailsService();
manager.setDataSource(dataSource);
return manager;
}
public static JdbcUserDetailsManager getUserDetailsService() {
JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
manager.setUsersByUsernameQuery("select login_name AS username, password,'1' AS enabled from im_user where login_name=?");
manager.setAuthoritiesByUsernameQuery("select login_name AS username, 'user' AS authority from im_user where login_name=?");
return manager;
}
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
/**
* 这一步的配置是必不可少的,否则SpringBoot会自动配置一个 AuthenticationManager,覆盖掉内存中的用户
*/
@Bean
@Override public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().csrf().disable();
}
}
业务系统(资源/授权)服务器的 spring security 安全配置如下:
@Order(1)
@Configuration
@EnableWebSecurity(debug=true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
SysUserDetailService userDetailService;
// 上传图片所在的路径
@Value("${UPLOAD_STATIC_PATH}")
private String UPLOAD_STATIC_PATH;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService)
.passwordEncoder(passwordEncoder());
}
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
/**
* 这一步的配置是必不可少的,否则SpringBoot会自动配置一个 AuthenticationManager,覆盖掉内存中的用户
*/
@Bean
@Override public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(WebSecurity web) throws Exception {
String[] ignoreList = {
UPLOAD_STATIC_PATH, "static/index.html", "favicon.ico", "/static/**"
};
web.ignoring().antMatchers(ignoreList);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
String[] permits = {"/oauth/**", "/oauths/**", "/lgn"};
http.authorizeRequests()
.antMatchers(permits).permitAll()
.and()
.formLogin()
.loginPage("/lgn")
.loginProcessingUrl("/user/lgn")
.successHandler((request, response, authentication) -> {
RequestDispatcher dispatcher = request.getRequestDispatcher("/user/loginSuccess");
dispatcher.forward(request, response);
})
.failureHandler((request, response, authentication) -> {
RequestDispatcher dispatcher = request.getRequestDispatcher("/user/loginError");
dispatcher.forward(request, response);
})
.permitAll()
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.csrf()
.disable();
http.logout()
.logoutUrl("/user/exit")
.logoutSuccessHandler(
new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
RequestDispatcher dispatcher = request.getRequestDispatcher("/user/exitSuccess");
dispatcher.forward(request, response);
}
});
}
}
下面给出业务系统的授权及资源配置:
@Configuration
@EnableAuthorizationServer()
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Resource
AuthenticationManager authenticationManager;
@Resource
private UserDetailsService userDetailsService;
public final static String clientId = "aaa";
public final static String scopes = "select";
public final static String rawPassword = "bbb";
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
String finalSecret = "{bcrypt}" + new BCryptPasswordEncoder().encode(rawPassword);
clients.inMemory().withClient(clientId)
.authorizedGrantTypes("password", "refresh_token", "authorization_code")
.autoApprove(true)
.scopes(scopes, "user")
.authorities("oauth2")
.secret(finalSecret)
.accessTokenValiditySeconds(7200)
;
}
@Bean
public TokenStore getTokenStore() {
return new InMemoryTokenStore();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager)
.tokenStore(getTokenStore())
.userDetailsService(userDetailsService)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
;
endpoints.tokenServices(tokenServices());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
// oauthServer.allowFormAuthenticationForClients();
oauthServer.allowFormAuthenticationForClients()
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
@Bean
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(getTokenStore());
return defaultTokenServices;
}
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {}
@Override
public void configure(HttpSecurity http) throws Exception {
String[] permits = {"/oauth/**", "/oauths/**", "/user/lgn"};
http
.authorizeRequests()
.antMatchers("/api/**")
.authenticated().and()
.authorizeRequests().antMatchers(permits).permitAll().and()
.cors().and()
.rememberMe().and()
.csrf().disable();
}
}
}
另外还有一点 @EnableResourceServer 注解已经加在业务系统(授权/资源端)的 spring boot 启动类.
聊天系统(客户端)启动报错如下:
java.lang.Object.wait(Native Method)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
com.mysql.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:40)
2020-09-19 11:37:21.673 ERROR 10832 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'UserController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'startTioRunner': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tioWsMsgHandler': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jwtTokenServices' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration$JwtTokenServicesConfiguration.class]: Unsatisfied dependency expressed through method 'jwtTokenServices' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jwtTokenStore' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration$JwtTokenServicesConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.provider.token.TokenStore]: Factory method 'jwtTokenStore' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jwtTokenEnhancer' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration$JwtTokenServicesConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter]: Factory method 'jwtTokenEnhancer' threw exception; nested exception is org.springframework.web.client.HttpClientErrorException$NotFound: 404 null
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:324)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1378)
at org.springframework.beans.factory.support.A